add javadocs to api classes, build javadoc & sources jars, allow multiple mixin configs to be declared, format

This commit is contained in:
moehreag 2024-06-03 23:46:30 +02:00
parent 4aec13abc2
commit 347ad2a58a
23 changed files with 535 additions and 173 deletions

View file

@ -18,6 +18,6 @@ jobs:
- name: Build - name: Build
run: | run: |
chmod +x ./gradlew chmod +x ./gradlew
./gradlew publishMavenJavaPublicationToFrogMCSnapshotsMavenRepository \ ./gradlew :publishMavenJavaPublicationToFrogMCSnapshotsMavenRepository \
-PFrogMCSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \ -PFrogMCSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \
-PFrogMCSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }} -PFrogMCSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }}

View file

@ -38,6 +38,13 @@ dependencies {
java { java {
sourceCompatibility = JavaVersion.VERSION_21 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21
withJavadocJar()
withSourcesJar()
}
tasks.javadoc {
include("**/api/**")
} }
tasks.processResources { tasks.processResources {

View file

@ -10,17 +10,6 @@ credits = [
{ name = "You", roles = ["author", "other_role"] } { name = "You", roles = ["author", "other_role"] }
] ]
[frog.dependencies]
depends = [
{ id = "other_mod", name = "Other Mod", versions = ">=0.2.0 <0.5.2 || 0.1.1 || 1.x || 3 || ~5 || ^6.x", link = "https://example.com" }
]
breaks = [
{ id = "old_mod", versions = "*" }
]
provides = [
{ id = "provided_mod", version = "that.version.aa" }
]
[frog.extensions] [frog.extensions]
pre_launch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension" pre_launch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
mixin_config = "example_mod.mixins.json" mixin_config = "example_mod.mixins.json"

View file

@ -1,7 +1,7 @@
package dev.frogmc.frogloader.api; package dev.frogmc.frogloader.api;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import dev.frogmc.frogloader.api.env.Env; import dev.frogmc.frogloader.api.env.Env;
@ -9,26 +9,92 @@ import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.plugin.FrogPlugin; import dev.frogmc.frogloader.api.plugin.FrogPlugin;
import dev.frogmc.frogloader.impl.FrogLoaderImpl; import dev.frogmc.frogloader.impl.FrogLoaderImpl;
/**
* General API to interact with this loader.
*
* @see ModProperties
* @see FrogPlugin
* @see Env
*/
public interface FrogLoader { public interface FrogLoader {
/**
* Get an instance of this loader.
*
* @return An instance of this loader
*/
static FrogLoader getInstance() { static FrogLoader getInstance() {
return FrogLoaderImpl.getInstance(); return FrogLoaderImpl.getInstance();
} }
List<FrogPlugin> getPlugins(); /**
* Get all loaded plugins.
*
* @return A collection of all loaded plugins
*/
Collection<FrogPlugin> getPlugins();
/**
* Get the current (physical) environment.
*
* @return The current environment
* @see Env
*/
Env getEnv(); Env getEnv();
/**
* Get the current game directory.
*
* @return The current game directory
* <p>Note: Should always be the current working directory, but this method should</p>
* be preferred over using the working directory directly.
*/
Path getGameDir(); Path getGameDir();
/**
* Get the current config directory.
*
* @return The current config directory
*/
Path getConfigDir(); Path getConfigDir();
/**
* Get the current mods directory.
*
* @return The current mods directory
*/
Path getModsDir(); Path getModsDir();
/**
* Query whether this loader is currently running in a development environment.
*
* @return Whether this loader is currently running in a development environment
*/
boolean isDevelopment(); boolean isDevelopment();
/**
* Query whether a specific mod is loaded.
*
* @param id The mod id to query for
* @return Whether a mod with this specific id is loaded
*/
boolean isModLoaded(String id); boolean isModLoaded(String id);
/**
* Get a mod's properties.
*
* @param id The mod id to query for
* @return An Optional containing the mod's properties, if it is present
* @see ModProperties
*/
Optional<ModProperties> getModProperties(String id); Optional<ModProperties> getModProperties(String id);
/**
* Get all loaded mods
*
* @return A collection of all loaded mods
* @see ModProperties
* @see FrogPlugin
*/
Collection<ModProperties> getMods();
} }

View file

@ -1,14 +1,40 @@
package dev.frogmc.frogloader.api.env; package dev.frogmc.frogloader.api.env;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter /**
* Physical environment constants
*/
@AllArgsConstructor @AllArgsConstructor
public enum Env { public enum Env {
/**
* The physical client environment
*/
CLIENT("CLIENT", "client"), CLIENT("CLIENT", "client"),
/**
* The physical (dedicated) server environment
*/
SERVER("SERVER", "server"), SERVER("SERVER", "server"),
; ;
final String mixinName, identifier;
private final String mixinName, identifier;
/**
* Get this environment's name, in the format Mixin understands.
*
* @return This environment's mixin name
*/
public String getMixinName() {
return this.mixinName;
}
/**
* Get this environment's identifier
*
* @return This environment's identifier
*/
public String getIdentifier() {
return this.identifier;
}
} }

View file

@ -1,13 +1,22 @@
package dev.frogmc.frogloader.api.extensions; package dev.frogmc.frogloader.api.extensions;
import dev.frogmc.frogloader.api.mod.ModExtensions;
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions; import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
/** /**
* The Pre-Launch Extension. * The Pre-Launch Extension.
* <p>This Extension is run right before the game is launched. (provided the used plugin supports it :) )</p> * <p>This Extension is run right before the game is launched. (provided the used plugin supports it :) )</p>
*
* @see ModExtensions
*/ */
public interface PreLaunchExtension { public interface PreLaunchExtension {
/**
* This extension's id. This is the key to use in your frog.mod.toml.
*/
String ID = BuiltinExtensions.PRE_LAUNCH; String ID = BuiltinExtensions.PRE_LAUNCH;
/**
* The initializer. This method will be invoked when this extension is run.
*/
void onPreLaunch(); void onPreLaunch();
} }

View file

@ -2,10 +2,36 @@ package dev.frogmc.frogloader.api.mod;
import java.util.*; import java.util.*;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* A mod's dependencies.
*
* <p>Mod dependencies are declared in four types:</p>
* <code>Type.DEPEND</code>, <code>Type.BREAK</code>, <code>Type.SUGGEST</code> and <code>Type.PROVIDE</code>
*
* <p>Note: The <code>Type.PROVIDE</code> is a bit of a special case in the way that it only supports a single SemVer version instead of a full version range.</p>
*
* @see ModDependencies.Type
* @see ModDependencies.Entry
* @see ModProperties
* @see SemVer
*/
public final class ModDependencies { public final class ModDependencies {
private final Map<Type, Collection<Entry>> entries = new HashMap<>(); private final Map<Type, Collection<Entry>> entries = new HashMap<>();
/**
* Construct new mod dependencies for a mod.
* <p><strong>Internal use only.</strong></p>
*
* @param depends <code>Type.DEPEND</code> entries
* @param breaks <code>Type.BREAK</code> entries
* @param suggests <code>Type.SUGGEST</code> entries
* @param provides <code>Type.PROVIDE</code> entries
*/
@ApiStatus.Internal
public ModDependencies(Collection<Entry> depends, Collection<Entry> breaks, Collection<Entry> suggests, Collection<Entry> provides) { public ModDependencies(Collection<Entry> depends, Collection<Entry> breaks, Collection<Entry> suggests, Collection<Entry> provides) {
entries.put(Type.DEPEND, depends); entries.put(Type.DEPEND, depends);
entries.put(Type.BREAK, breaks); entries.put(Type.BREAK, breaks);
@ -13,51 +39,120 @@ public final class ModDependencies {
entries.put(Type.PROVIDE, provides); entries.put(Type.PROVIDE, provides);
} }
/**
* Get dependencies of a specific mod for a specific type
*
* @param type the dependency type to query for
* @return a collection of dependency entries
*/
public Collection<Entry> getForType(Type type) { public Collection<Entry> getForType(Type type) {
return entries.get(type); return entries.get(type);
} }
public List<ModEntry> getForModId(String id) { /**
* Get entries that depend on a specific mod's id
*
* @param id the mod id to find dependency entries for
* @return a collection of entries that depend on the given mod id
*/
public Collection<ModEntry> getForModId(String id) {
List<ModEntry> entries = new ArrayList<>(); List<ModEntry> entries = new ArrayList<>();
for (Type type : Type.values()) { for (Type type : Type.values()) {
for (Entry entry : getForType(type)) { for (Entry entry : getForType(type)) {
if (entry.id.equals(id)) { if (entry.id.equals(id)) {
entries.add(new ModEntry(type, entry.range, entry.link)); entries.add(new ModEntry(type, entry.range, entry.link, entry.name));
} }
} }
} }
return entries; return entries;
} }
/**
* Dependency types to distinguish their variants.
*/
public enum Type {
/**
* Depend on another mod
*/
DEPEND,
/**
* Declare another mod as breaking with your own
*/
BREAK,
/**
* Suggest a user to install another mod
*/
SUGGEST,
/**
* Declare your mod to provide another mod
*/
PROVIDE
}
/**
* A data class to handle entries depending on a specific mod id
*/
public static class ModEntry { public static class ModEntry {
private final Type type; private final Type type;
private final String range; private final String range;
private final String link; private final String link;
private final String name;
private ModEntry(Type type, String range, String link) { private ModEntry(Type type, String range, String link, String name) {
this.type = type; this.type = type;
this.range = range; this.range = range;
this.link = link; this.link = link;
this.name = name;
} }
/**
* Get this dependency's type
*
* @return This dependency's type
*/
public Type type() { public Type type() {
return type; return type;
} }
/**
* Get this dependency's version range
*
* @return This dependency's version range
*/
public String range() { public String range() {
return range; return range;
} }
public String link(){ /**
* Get this dependency's link
* <p>May be null.</p>
*
* @return This dependency's link
*/
public @Nullable String link() {
return link; return link;
} }
/**
* Get this dependency's (friendly) name
* <p>May be null.</p>
*
* @return This dependency's name
*/
public @Nullable String name() {
return name;
}
} }
/**
* General storage for dependencies
*
* @param id the mod's id
* @param range the dependency's version range
* @param link an optional link for information about this dependency
* @param name an optional (friendly) name for this dependency
*/
public record Entry(String id, String range, String link, String name) { public record Entry(String id, String range, String link, String name) {
} }
public enum Type {
DEPEND, BREAK, SUGGEST, PROVIDE
}
} }

View file

@ -9,42 +9,90 @@ import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/**
* This class stores a mod's extensions.
* <p>An extension is a simple key-value mapping of a string name to any value.</p>
* <p>This class further provides utility methods to easily work with extension values of various types,
* especially for extensions providing some form of class or method reference.</p>
*
* @see ModProperties
*/
public final class ModExtensions { public final class ModExtensions {
private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class); private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class);
public static ModExtensions of(Map<String, Object> entries){
return new ModExtensions(entries);
}
private final Map<String, Object> extensions; private final Map<String, Object> extensions;
private ModExtensions(Map<String, Object> entries){ private ModExtensions(Map<String, Object> entries) {
extensions = entries; extensions = entries;
} }
/**
* Retrieve a new instance of this class.
* <p><strong>Internal use only.</strong></p>
*
* @param entries the entries read from the mod's properties file
* @return an instance of this class
*/
public static ModExtensions of(Map<String, Object> entries) {
return new ModExtensions(entries);
}
/**
* Get the value of the provided extension name.
*
* @param key The extension name to query
* @param <T> The type of the value of this extension
* @return The value of the extension, or null
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(String key) { public <T> T get(String key) {
return (T) extensions.get(key); return (T) extensions.get(key);
} }
/**
* Get the value of the provided extension name.
*
* @param key The extension name to query
* @param defaultValue a default value
* @param <T> The type of the value of this extension
* @return The value of the extension, or the default value if it isn't present
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getOrDefault(String key, T defaultValue) { public <T> T getOrDefault(String key, T defaultValue) {
return (T) extensions.getOrDefault(key, defaultValue); return (T) extensions.getOrDefault(key, defaultValue);
} }
/**
* Run the given action on this extension if it is present.
*
* @param key the extension name to query
* @param action the action to run on the value of the extension if it is present
* @param <T> The type of the value of this extension
*/
public <T> void runIfPresent(String key, Consumer<T> action) { public <T> void runIfPresent(String key, Consumer<T> action) {
T value = get(key); T value = get(key);
if (value != null){ if (value != null) {
action.accept(value); action.accept(value);
} }
} }
/**
* Run the given action on this extension if it is present.
* <p>This method simplifies handling references to classes or methods.</p>
* It will first query the string value of the extension and, if it is found, create a new instance of the referenced class
* or invoke the referenced method to retrieve an instance of the provided class. Then the provided action is run on this
* object.
*
* @param key The name of the extension
* @param type The class type of the extension (The class the extension class is extending/implementing)
* @param action The action to run on the newly retrieved instance of the provided class
* @param <T> The type of the class
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) { public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) {
String value = get(key); String value = get(key);
if (value == null){ if (value == null) {
return; return;
} }

View file

@ -5,23 +5,75 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/**
* A mod's properties. This class represents a mod at runtime.
* It is read from each mod's <code>frog.mod.toml</code> file.
*
* @see ModDependencies
* @see ModExtensions
* @see SemVer
*/
public interface ModProperties { public interface ModProperties {
/**
* Get this mod's id.
*
* @return The mod's id
*/
String id(); String id();
/**
* Get this mod's name.
*
* @return The mod's name
*/
String name(); String name();
/**
* Get this mod's icon.
* <p>May be null if the mod doesn't specify an icon.</p>
*
* @return The mod's icon
*/
@Nullable @Nullable
String icon(); String icon();
/**
* Get this mod's version.
*
* @return The mod's version
*/
SemVer version(); SemVer version();
/**
* Get this mod's license.
*
* @return The mod's license
*/
String license(); String license();
/**
* Get credits for this mod.
* <p>This is a map containing people's names as keys and their roles in this mod as values.</p>
*
* @return The mod's credits.
*/
Map<String, Collection<String>> credits(); Map<String, Collection<String>> credits();
/**
* Get this mod's dependencies.
*
* @return The mod's dependencies
* @see ModDependencies
*/
ModDependencies dependencies(); ModDependencies dependencies();
/**
* Get this mod's declared extensions.
*
* @return The mod's extensions
* @see ModExtensions
*/
ModExtensions extensions(); ModExtensions extensions();

View file

@ -1,11 +1,46 @@
package dev.frogmc.frogloader.api.mod; package dev.frogmc.frogloader.api.mod;
/**
* Simple access to SemVer-style versions. This is used for versioning mods.
*
* @see ModProperties
*/
public interface SemVer extends Comparable<SemVer> { public interface SemVer extends Comparable<SemVer> {
/**
* Get this version's major version component.
*
* @return This version's major version component
*/
int major(); int major();
/**
* Get this version's minor version component.
*
* @return This version's minor version component
*/
int minor(); int minor();
/**
* Get this version's patch version component.
*
* @return This version's patch version component
*/
int patch(); int patch();
/**
* Get this version's pre-release version component.
*
* @return This version's pre-release version component
*/
String prerelease(); String prerelease();
/**
* Get this version's build version component.
*
* @return This version's build version component
*/
String build(); String build();
@Override
boolean equals(Object other); boolean equals(Object other);
} }

View file

@ -6,23 +6,52 @@ import java.util.Collections;
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;
/**
* A Plugin that may load mods for a specific game and environment
*
* @see FrogLoader
*/
public interface FrogPlugin { public interface FrogPlugin {
/**
* General run method of this plugin. This method is run after all plugins have been initialized.
*
* @see FrogPlugin#init(FrogLoader)
* @see FrogPlugin#getMods()
*/
default void run() { default void run() {
} }
/** /**
* Check whether this plugin is applicable for the current environment
*
* @return Whether this plugin is applicable to be loaded in the current environment * @return Whether this plugin is applicable to be loaded in the current environment
*/ */
default boolean isApplicable() { default boolean isApplicable() {
return false; return false;
} }
/**
* Initialization method for this plugin. This method will be called after <code>isApplicable()</code>
* if it returns true to initialize is plugin.
*
* @param loader the loader loading this plugin
* @throws Exception This method may throw any exception, it will be handled by the loader.
* @see FrogPlugin#isApplicable()
* @see FrogPlugin#getMods()
*/
default void init(FrogLoader loader) throws Exception { default void init(FrogLoader loader) throws Exception {
} }
default Collection<ModProperties> getMods(){ /**
* This method should return all mods loaded by this plugin. It will be queried after <code>init(FrogLoader)</code>
* and before <code>run()</code>.
*
* @return A collection of mods loaded by this plugin.
* @see FrogPlugin#init(FrogLoader)
* @see FrogPlugin#run()
*/
default Collection<ModProperties> getMods() {
return Collections.emptySet(); return Collections.emptySet();
} }
} }

View file

@ -27,9 +27,9 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
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);
@Getter @Getter
private static FrogLoaderImpl instance; private static FrogLoaderImpl instance;
private final boolean DEV_ENV = Boolean.getBoolean(SystemProperties.DEVELOPMENT);
@Getter @Getter
private final String[] args; private final String[] args;
@Getter @Getter
@ -78,7 +78,7 @@ public class FrogLoaderImpl implements FrogLoader {
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.builder().setContent(LoaderGui.ContentType.GENERIC_ERROR, t).addReport(CrashReportGenerator.writeReport(t, collectMods().values())).build().show(); LoaderGui.builder().setContent(LoaderGui.ContentType.GENERIC_ERROR, t).addReport(CrashReportGenerator.writeReport(t, collectMods().values())).build().show();
} }
} }
@ -164,7 +164,8 @@ public class FrogLoaderImpl implements FrogLoader {
return mods.keySet(); return mods.keySet();
} }
public Collection<ModProperties> getMods(){ @Override
public Collection<ModProperties> getMods() {
return mods.values(); return mods.values();
} }
} }

View file

@ -26,44 +26,14 @@ import org.jetbrains.annotations.Nullable;
@AllArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE)
public class LoaderGui { public class LoaderGui {
public static Builder builder(){
return new Builder();
}
public static class Builder {
private Path report;
private Consumer<JFrame> contentFunc;
private boolean keepRunning = false;
public <T> Builder setContent(ContentType<T> type, T argument){
contentFunc = f -> type.contentSetter.accept(f, argument);
return this;
}
public Builder addReport(Path report){
this.report = report;
return this;
}
public Builder keepRunning(){
keepRunning = true;
return this;
}
public LoaderGui build(){
JFrame frame = getFrame(report);
if (contentFunc != null) {
contentFunc.accept(frame);
}
return new LoaderGui(frame, keepRunning);
}
}
private final JFrame frame; private final JFrame frame;
private final boolean keepRunning; private final boolean keepRunning;
private static JFrame getFrame(Path report){ public static Builder builder() {
return new Builder();
}
private static JFrame getFrame(Path report) {
var frame = new JFrame(); var frame = new JFrame();
frame.setTitle("FrogLoader " + LoaderGui.class.getPackage().getImplementationVersion()); frame.setTitle("FrogLoader " + LoaderGui.class.getPackage().getImplementationVersion());
frame.setSize(952, 560); frame.setSize(952, 560);
@ -103,8 +73,51 @@ public class LoaderGui {
return frame; return frame;
} }
private static String printVersionRange(ModDependencyResolver.VersionRange range) {
return "range: \n" + range.toString().replace("||", "or");
}
private static JPanel getEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon, List<JButton> actions) {
JPanel entry = new JPanel(new BorderLayout());
entry.setBorder(BasicBorders.getInternalFrameBorder());
Box text = Box.createVerticalBox();
text.setBorder(BorderFactory.createEmptyBorder());
JTextPane desc = new JTextPane();
desc.setContentType("text/html");
desc.setEditable(false);
desc.setBackground(background);
desc.setText("<html>" + description.replace("<", "&lt;").replace("\n", "<br>") + "</html>");
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
actions.forEach(actionPanel::add);
if (icon != null) {
URL location = LoaderGui.class.getResource(icon);
if (location != null) {
entry.add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
}
}
text.add(desc);
JCheckBox showAdvanced = new JCheckBox("Show raw version range");
JTextPane advanced = new JTextPane();
advanced.setEditable(false);
advanced.setBackground(background);
advanced.setText(range.toString());
advanced.setVisible(false);
text.add(advanced);
showAdvanced.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
advanced.setVisible(showAdvanced.isSelected());
}
});
entry.add(text);
Box bottom = Box.createHorizontalBox();
bottom.add(showAdvanced, actionPanel);
entry.add(bottom, BorderLayout.SOUTH);
return entry;
}
@SuppressWarnings("BusyWait") @SuppressWarnings("BusyWait")
public void show(){ public void show() {
frame.setVisible(true); frame.setVisible(true);
while (frame.isVisible()) { while (frame.isVisible()) {
@ -119,6 +132,36 @@ public class LoaderGui {
} }
} }
public static class Builder {
private Path report;
private Consumer<JFrame> contentFunc;
private boolean keepRunning = false;
public <T> Builder setContent(ContentType<T> type, T argument) {
contentFunc = f -> type.contentSetter.accept(f, argument);
return this;
}
public Builder addReport(Path report) {
this.report = report;
return this;
}
public Builder keepRunning() {
keepRunning = true;
return this;
}
public LoaderGui build() {
JFrame frame = getFrame(report);
if (contentFunc != null) {
contentFunc.accept(frame);
}
return new LoaderGui(frame, keepRunning);
}
}
@AllArgsConstructor @AllArgsConstructor
public static class ContentType<T> { public static class ContentType<T> {
public static final ContentType<ModDependencyResolver.UnfulfilledDependencyException> INFO_UNFULFILLED_DEP = new ContentType<>((frame, ex) -> { public static final ContentType<ModDependencyResolver.UnfulfilledDependencyException> INFO_UNFULFILLED_DEP = new ContentType<>((frame, ex) -> {
@ -127,7 +170,7 @@ public class LoaderGui {
title.setBackground(pane.getBackground()); title.setBackground(pane.getBackground());
title.setEditable(false); title.setEditable(false);
int size = ex.getDependencies().size(); int size = ex.getDependencies().size();
title.setText("Found "+size+" Error"+(size > 1 ? "s:":":")); title.setText("Found " + size + " Error" + (size > 1 ? "s:" : ":"));
title.setFont(title.getFont().deriveFont(Font.BOLD, 16f)); title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
title.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 0)); title.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 0));
pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
@ -136,9 +179,9 @@ public class LoaderGui {
Box list = Box.createVerticalBox(); Box list = Box.createVerticalBox();
ex.getDependencies().forEach(e -> { ex.getDependencies().forEach(e -> {
StringBuilder description = new StringBuilder(); StringBuilder description = new StringBuilder();
if (e.presentVersion() != null){ if (e.presentVersion() != null) {
description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on "); description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on ");
if (e.dependencyName() != null){ if (e.dependencyName() != null) {
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") "); description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
} else { } else {
description.append("a Mod with id ").append(e.dependency()); description.append("a Mod with id ").append(e.dependency());
@ -146,7 +189,7 @@ public class LoaderGui {
description.append(" with a version matching ").append(printVersionRange(e.range())).append(", but a different version is present or provided: ").append(e.presentVersion()); description.append(" with a version matching ").append(printVersionRange(e.range())).append(", but a different version is present or provided: ").append(e.presentVersion());
} else { } else {
description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on "); description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on ");
if (e.dependencyName() != null){ if (e.dependencyName() != null) {
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") "); description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
} else { } else {
description.append("a Mod with id ").append(e.dependency()); description.append("a Mod with id ").append(e.dependency());
@ -157,7 +200,7 @@ public class LoaderGui {
.append(e.range().maxCompatible().or(e.range()::minCompatible).map(Objects::toString) .append(e.range().maxCompatible().or(e.range()::minCompatible).map(Objects::toString)
.map(s -> "0.0.0".equals(s) ? "any version" : "version " + s).orElse("<unknown>")) .map(s -> "0.0.0".equals(s) ? "any version" : "version " + s).orElse("<unknown>"))
.append(" of "); .append(" of ");
if (e.dependencyName() != null){ if (e.dependencyName() != null) {
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") "); description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
} else { } else {
description.append("Mod with id ").append(e.dependency()); description.append("Mod with id ").append(e.dependency());
@ -169,7 +212,7 @@ public class LoaderGui {
JButton urlButton = new JButton(new AbstractAction(name) { JButton urlButton = new JButton(new AbstractAction(name) {
@Override @Override
public void actionPerformed(ActionEvent event) { public void actionPerformed(ActionEvent event) {
if (install){ if (install) {
ModUtil.installMod(e.link()); ModUtil.installMod(e.link());
} else { } else {
URLUtil.open(URI.create(e.link())); URLUtil.open(URI.create(e.link()));
@ -190,7 +233,7 @@ public class LoaderGui {
title.setBackground(pane.getBackground()); title.setBackground(pane.getBackground());
title.setEditable(false); title.setEditable(false);
int size = ex.getBreaks().size(); int size = ex.getBreaks().size();
title.setText("Found "+size+" Error"+(size > 1 ? "s:":":")); title.setText("Found " + size + " Error" + (size > 1 ? "s:" : ":"));
title.setFont(title.getFont().deriveFont(Font.BOLD, 16f)); title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
pane.add(title, BorderLayout.NORTH); pane.add(title, BorderLayout.NORTH);
@ -235,47 +278,4 @@ public class LoaderGui {
private final BiConsumer<JFrame, T> contentSetter; private final BiConsumer<JFrame, T> contentSetter;
} }
private static String printVersionRange(ModDependencyResolver.VersionRange range){
return "range: \n"+ range.toString().replace("||", "or");
}
private static JPanel getEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon, List<JButton> actions){
JPanel entry = new JPanel(new BorderLayout());
entry.setBorder(BasicBorders.getInternalFrameBorder());
Box text = Box.createVerticalBox();
text.setBorder(BorderFactory.createEmptyBorder());
JTextPane desc = new JTextPane();
desc.setContentType("text/html");
desc.setEditable(false);
desc.setBackground(background);
desc.setText("<html>" + description.replace("<", "&lt;").replace("\n", "<br>") + "</html>");
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
actions.forEach(actionPanel::add);
if (icon != null) {
URL location = LoaderGui.class.getResource(icon);
if (location != null) {
entry.add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
}
}
text.add(desc);
JCheckBox showAdvanced = new JCheckBox("Show raw version range");
JTextPane advanced = new JTextPane();
advanced.setEditable(false);
advanced.setBackground(background);
advanced.setText(range.toString());
advanced.setVisible(false);
text.add(advanced);
showAdvanced.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
advanced.setVisible(showAdvanced.isSelected());
}
});
entry.add(text);
Box bottom = Box.createHorizontalBox();
bottom.add(showAdvanced, actionPanel);
entry.add(bottom, BorderLayout.SOUTH);
return entry;
}
} }

View file

@ -26,7 +26,11 @@ public class AccessWidener {
get().loadFromData(data); get().loadFromData(data);
} }
private void loadFromData(Data data){ public static byte[] processClass(byte[] classBytes, String className) {
return get().process(classBytes, className);
}
private void loadFromData(Data data) {
classMap.putAll(data.classMap); classMap.putAll(data.classMap);
methods.putAll(data.methods); methods.putAll(data.methods);
fields.putAll(data.fields); fields.putAll(data.fields);
@ -34,12 +38,8 @@ public class AccessWidener {
classNames.addAll(data.classNames); classNames.addAll(data.classNames);
} }
public static byte[] processClass(byte[] classBytes, String className) {
return get().process(classBytes, className);
}
private byte[] process(byte[] classBytes, String className) { private byte[] process(byte[] classBytes, String className) {
if (!classNames.contains(className)){ if (!classNames.contains(className)) {
return classBytes; return classBytes;
} }
@ -97,23 +97,6 @@ public class AccessWidener {
return writer.toByteArray(); return writer.toByteArray();
} }
public record Data(Map<String, Entry> classMap,
Map<String, Map<String, Entry>> methods,
Map<String, Map<String, Entry>> fields,
Map<String, Map<String, Entry>> mutations,
Set<String> classNames) {}
public record Entry(AccessType type, String targetType, String className, String name, String descriptor) {
public Entry(String[] line) {
this(AccessType.of(line[0]), line[1], line[2], line[3], line[4]);
}
public boolean isAccessGreaterThan(Entry other) {
return Access.of(type().access).index < Access.of(other.type.access).index;
}
}
@AllArgsConstructor @AllArgsConstructor
public enum AccessType { public enum AccessType {
ACCESSIBLE("accessible", Opcodes.ACC_PUBLIC), ACCESSIBLE("accessible", Opcodes.ACC_PUBLIC),
@ -146,4 +129,22 @@ public class AccessWidener {
} }
} }
public record Data(Map<String, Entry> classMap,
Map<String, Map<String, Entry>> methods,
Map<String, Map<String, Entry>> fields,
Map<String, Map<String, Entry>> mutations,
Set<String> classNames) {
}
public record Entry(AccessType type, String targetType, String className, String name, String descriptor) {
public Entry(String[] line) {
this(AccessType.of(line[0]), line[1], line[2], line[3], line[4]);
}
public boolean isAccessGreaterThan(Entry other) {
return Access.of(type().access).index < Access.of(other.type.access).index;
}
}
} }

View file

@ -16,12 +16,13 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
public class MixinClassLoader extends URLClassLoader { public class MixinClassLoader extends URLClassLoader {
private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader(); private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader();
private final List<String> exclusions = new ArrayList<>();
static { static {
registerAsParallelCapable(); registerAsParallelCapable();
} }
private final List<String> exclusions = new ArrayList<>();
public MixinClassLoader() { public MixinClassLoader() {
super(new URL[0], null); super(new URL[0], null);
excludePackage("java"); excludePackage("java");

View file

@ -5,7 +5,7 @@ import dev.frogmc.frogloader.impl.launch.FrogLauncher;
public class FrogClient { public class FrogClient {
public static void main(String[] args){ public static void main(String[] args) {
FrogLauncher.run(args, Env.CLIENT); FrogLauncher.run(args, Env.CLIENT);
} }
} }

View file

@ -5,7 +5,7 @@ import dev.frogmc.frogloader.impl.launch.FrogLauncher;
public class FrogServer { public class FrogServer {
public static void main(String[] args){ public static void main(String[] args) {
FrogLauncher.run(args, Env.SERVER); FrogLauncher.run(args, Env.SERVER);
} }
} }

View file

@ -139,9 +139,6 @@ public class ModDependencyResolver {
return result; return result;
} }
private record ProvidedMod(String modId, SemVer version, ModProperties source) {
}
@AllArgsConstructor @AllArgsConstructor
private enum DependencyType { private enum DependencyType {
EQ("=", Object::equals), EQ("=", Object::equals),
@ -163,6 +160,8 @@ public class ModDependencyResolver {
} }
} }
private record ProvidedMod(String modId, SemVer version, ModProperties source) {
}
@Getter @Getter
public static class BreakingModException extends Exception { public static class BreakingModException extends Exception {

View file

@ -14,22 +14,22 @@ public class ModUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ModUtil.class); private static final Logger LOGGER = LoggerFactory.getLogger(ModUtil.class);
public static String getModList(Collection<ModProperties> mods){ public static String getModList(Collection<ModProperties> mods) {
int size = mods.size(); int size = mods.size();
if (size == 0){ if (size == 0) {
return "No mods loaded."; return "No mods loaded.";
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("Loaded ").append(size).append(" mod"); builder.append("Loaded ").append(size).append(" mod");
if (size > 1){ if (size > 1) {
builder.append("s"); builder.append("s");
} }
builder.append(":"); builder.append(":");
int i = 0; int i = 0;
for (ModProperties p : mods) { for (ModProperties p : mods) {
builder.append("\n\t"); builder.append("\n\t");
if (i < size-1) { if (i < size - 1) {
builder.append("|- "); builder.append("|- ");
} else { } else {
builder.append("\\- "); builder.append("\\- ");

View file

@ -51,7 +51,7 @@ public record SemVerImpl(int major, int minor, int patch, String prerelease, Str
if (obj instanceof SemVerImpl s) { if (obj instanceof SemVerImpl s) {
return compareTo(s) == 0; return compareTo(s) == 0;
} }
return false; return toString().equals(obj.toString());
} }
@Override @Override
@ -74,7 +74,7 @@ public record SemVerImpl(int major, int minor, int patch, String prerelease, Str
} }
} }
if (prerelease == null){ if (prerelease == null) {
return 0; return 0;
} }

View file

@ -48,6 +48,7 @@ public class Minecraft implements FrogPlugin {
return gamePath != null; return gamePath != null;
} }
@SuppressWarnings({"rawtypes", "unchecked"})
@Override @Override
public void init(FrogLoader loader) throws Exception { public void init(FrogLoader loader) throws Exception {
if (gamePath == null) { if (gamePath == null) {
@ -98,9 +99,11 @@ public class Minecraft implements FrogPlugin {
}).forEach(FrogLoaderImpl.getInstance().getClassloader()::addURL); }).forEach(FrogLoaderImpl.getInstance().getClassloader()::addURL);
modProperties.forEach(props -> { modProperties.forEach(props -> {
String name = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG); Object o = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
if (name != null) { if (o instanceof String name) {
Mixins.addConfiguration(name); Mixins.addConfiguration(name);
} else if (o instanceof Collection l) {
((Collection<String>) l).forEach(Mixins::addConfiguration);
} }
}); });

View file

@ -5,7 +5,8 @@ import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.*; import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Collection; import java.util.Collection;
@ -24,7 +25,7 @@ public class CrashReportGenerator {
} }
public static Path writeReport(Throwable t, Collection<ModProperties> mods) { public static Path writeReport(Throwable t, Collection<ModProperties> mods) {
String fileName = "crash-"+dateFormat.format(ZonedDateTime.now())+"_frogloader.log"; String fileName = "crash-" + dateFormat.format(ZonedDateTime.now()) + "_frogloader.log";
Path out = FrogLoaderImpl.getInstance().getGameDir().resolve("crash-reports").resolve(fileName); Path out = FrogLoaderImpl.getInstance().getGameDir().resolve("crash-reports").resolve(fileName);
try { try {
@ -40,7 +41,7 @@ public class CrashReportGenerator {
return out; return out;
} }
public static String getForException(Throwable t){ public static String getForException(Throwable t) {
return getForException(t, FrogLoaderImpl.getInstance().getMods()); return getForException(t, FrogLoaderImpl.getInstance().getMods());
} }