Merge pull request 'add provides dependency type, add accesswidener processing, jij mod loading' (#10) from owlsys/aw-jij into main
Reviewed-on: https://git-esnesnon.ecorous.org/esnesnon/FrogLoader/pulls/10 Reviewed-by: Ecorous <ecorous@outlook.com> Reviewed-by: TheKodeToad <kode@noreply.localhost>
This commit is contained in:
commit
36260356e2
|
@ -23,13 +23,15 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.remapper)
|
implementation(libs.remapper){
|
||||||
|
isTransitive = false
|
||||||
|
}
|
||||||
compileOnly("org.apache.logging.log4j:log4j-slf4j2-impl:3.0.0-beta2")
|
compileOnly("org.apache.logging.log4j:log4j-slf4j2-impl:3.0.0-beta2")
|
||||||
compileOnly("org.apache.logging.log4j:log4j-api:3.0.0-beta2")
|
compileOnly("org.apache.logging.log4j:log4j-api:3.0.0-beta2")
|
||||||
compileOnly("org.apache.logging.log4j:log4j-core:3.0.0-beta2")
|
compileOnly("org.apache.logging.log4j:log4j-core:3.0.0-beta2")
|
||||||
|
|
||||||
api(libs.mixin)
|
api(libs.mixin)
|
||||||
api(libs.nightconfig)
|
implementation(libs.nightconfig)
|
||||||
api(libs.annotations)
|
api(libs.annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
remapper = "1.0.0-SNAPSHOT"
|
remapper = "1.0.0-SNAPSHOT"
|
||||||
nightconfig = "3.7.1"
|
nightconfig = "3.7.2"
|
||||||
mixin = "0.13.4+mixin.0.8.5"
|
mixin = "0.13.4+mixin.0.8.5"
|
||||||
annotations = "24.1.0"
|
annotations = "24.1.0"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import org.ecorous.esnesnon.gradle.ext.minecraft
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
id("org.ecorous.esnesnon.nonsense-gradle").version("0.0.1-SNAPSHOT")
|
id("org.ecorous.esnesnon.phytotelma") version "0.0.1-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -21,5 +21,9 @@ minecraft("1.20.6")
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":"))
|
implementation(project(":"))
|
||||||
annotationProcessor(libs.mixin)
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
5
minecraft/src/main/resources/example_mod.accesswidener
Normal file
5
minecraft/src/main/resources/example_mod.accesswidener
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
accessWidener v2 named
|
||||||
|
|
||||||
|
accessible method net/minecraft/world/level/block/TransparentBlock codec ()Lcom/mojang/serialization/MapCodec;
|
||||||
|
mutable field net/minecraft/client/gui/GuiSpriteManager METADATA_SECTIONS Ljava/util/Set;
|
||||||
|
mutable field net/minecraft/client/gui/GuiGraphics$ScissorStack stack Ljava/util/Deque;
|
|
@ -21,4 +21,5 @@ breaks = [
|
||||||
[frog.extensions]
|
[frog.extensions]
|
||||||
pre_launch = "org.ecorous.esnesnon.nonsense.loader.example.ExamplePreLaunchExtension"
|
pre_launch = "org.ecorous.esnesnon.nonsense.loader.example.ExamplePreLaunchExtension"
|
||||||
mixin_config = "example_mod.mixins.json"
|
mixin_config = "example_mod.mixins.json"
|
||||||
|
frog_aw = "example_mod.accesswidener"
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ pluginManagement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "nonsense-loader"
|
rootProject.name = "frogloader"
|
||||||
|
|
||||||
include(":minecraft")
|
include(":minecraft")
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package org.ecorous.esnesnon.nonsense.loader.api.extensions;
|
package org.ecorous.esnesnon.nonsense.loader.api.extensions;
|
||||||
|
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.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>
|
||||||
*/
|
*/
|
||||||
public interface PreLaunchExtension {
|
public interface PreLaunchExtension {
|
||||||
String ID = "pre_launch";
|
String ID = BuiltinExtensions.PRE_LAUNCH;
|
||||||
|
|
||||||
void onPreLaunch();
|
void onPreLaunch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
package org.ecorous.esnesnon.nonsense.loader.api.mod;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.LoaderImpl;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public final class License {
|
|
||||||
|
|
||||||
private static final Map<String, String> idToName = new HashMap<>();
|
|
||||||
private static final Map<String, License> idToLicense = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
loadList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void loadList(){
|
|
||||||
try (InputStream in = License.class.getResourceAsStream("/assets/nonsense-loader/licenses.json")){
|
|
||||||
if (in == null){
|
|
||||||
throw new IllegalStateException("in == null");
|
|
||||||
}
|
|
||||||
JsonObject object = LoaderImpl.getInstance().getGson().fromJson(new InputStreamReader(in), JsonObject.class);
|
|
||||||
object.getAsJsonArray("licenses").forEach(element -> {
|
|
||||||
JsonObject entry = element.getAsJsonObject();
|
|
||||||
idToName.put(entry.get("licenseId").getAsString(), entry.get("name").getAsString());
|
|
||||||
});
|
|
||||||
} catch (Exception e){
|
|
||||||
LoggerFactory.getLogger(License.class).warn("Failed to load license list!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License fromId(String id){
|
|
||||||
return idToLicense.computeIfAbsent(id, ignored -> new License(idToName.getOrDefault(id, id), id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License custom(String id, String name){
|
|
||||||
return idToLicense.computeIfAbsent(id, ignored -> new License(idToName.computeIfAbsent(id, s -> name), id));
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String name, id;
|
|
||||||
private License(String name, String id) {
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String id() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ import java.util.*;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public final class ModCredits implements Map<String, Collection<String>> {
|
public final class ModCredits {
|
||||||
|
|
||||||
public static ModCredits of(Map<String, Collection<String>> credits){
|
public static ModCredits of(Map<String, Collection<String>> credits){
|
||||||
return new ModCredits(credits);
|
return new ModCredits(credits);
|
||||||
|
@ -23,63 +23,15 @@ public final class ModCredits implements Map<String, Collection<String>> {
|
||||||
return credits.getOrDefault(name, Collections.emptySet());
|
return credits.getOrDefault(name, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
public int size() {
|
||||||
return credits.size();
|
return credits.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return credits.isEmpty();
|
return credits.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public @NotNull Set<Map.Entry<String, Collection<String>>> entrySet() {
|
||||||
public boolean containsKey(Object key) {
|
|
||||||
return credits.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsValue(Object value) {
|
|
||||||
return credits.containsValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> get(Object key) {
|
|
||||||
return credits.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> put(String key, Collection<String> value) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> remove(Object key) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putAll(@NotNull Map<? extends String, ? extends Collection<String>> m) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Set<String> keySet() {
|
|
||||||
return credits.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Collection<Collection<String>> values() {
|
|
||||||
return credits.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Set<Entry<String, Collection<String>>> entrySet() {
|
|
||||||
return credits.entrySet();
|
return credits.entrySet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,11 @@ public final class ModDependencies {
|
||||||
|
|
||||||
private final Map<Type, Collection<Entry>> entries = new HashMap<>();
|
private final Map<Type, Collection<Entry>> entries = new HashMap<>();
|
||||||
|
|
||||||
public ModDependencies(Collection<Entry> depends, Collection<Entry> breaks, Collection<Entry> suggests) {
|
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);
|
||||||
entries.put(Type.SUGGEST, suggests);
|
entries.put(Type.SUGGEST, suggests);
|
||||||
|
entries.put(Type.PROVIDE, provides);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Entry> getForType(Type type) {
|
public Collection<Entry> getForType(Type type) {
|
||||||
|
@ -51,6 +52,6 @@ public final class ModDependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
DEPEND, BREAK, SUGGEST
|
DEPEND, BREAK, SUGGEST, PROVIDE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.ecorous.esnesnon.nonsense.loader.api.mod;
|
package org.ecorous.esnesnon.nonsense.loader.api.mod;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface ModProperties {
|
public interface ModProperties {
|
||||||
|
|
||||||
String id();
|
String id();
|
||||||
|
@ -8,9 +11,9 @@ public interface ModProperties {
|
||||||
|
|
||||||
SemVer version();
|
SemVer version();
|
||||||
|
|
||||||
License license();
|
String license();
|
||||||
|
|
||||||
ModCredits credits();
|
Map<String, Collection<String>> credits();
|
||||||
|
|
||||||
ModDependencies dependencies();
|
ModDependencies dependencies();
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,11 @@ public class LoaderImpl implements Loader {
|
||||||
LOGGER.error("Error during plugin initialisation: ", e);
|
LOGGER.error("Error during plugin initialisation: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (plugins.isEmpty()){
|
||||||
|
// TODO display error
|
||||||
|
throw new IllegalStateException("No plugin applicable to the current state was found!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getArgument(String name){
|
public String getArgument(String name){
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense.loader.impl.launch;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.objectweb.asm.*;
|
||||||
|
|
||||||
|
public class AccessWidener {
|
||||||
|
private static final AccessWidener INSTANCE = new AccessWidener();
|
||||||
|
|
||||||
|
Map<String, Entry> classMap = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, Entry>> methods = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, Entry>> fields = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, Entry>> mutations = new ConcurrentHashMap<>();
|
||||||
|
Set<String> classNames = new ConcurrentSkipListSet<>();
|
||||||
|
|
||||||
|
private static AccessWidener get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(Data data) {
|
||||||
|
get().loadFromData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromData(Data data){
|
||||||
|
classMap.putAll(data.classMap);
|
||||||
|
methods.putAll(data.methods);
|
||||||
|
fields.putAll(data.fields);
|
||||||
|
mutations.putAll(data.mutations);
|
||||||
|
classNames.addAll(data.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] processClass(byte[] classBytes, String className) {
|
||||||
|
return get().process(classBytes, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] process(byte[] classBytes, String className) {
|
||||||
|
if (!classNames.contains(className)){
|
||||||
|
return classBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassReader reader = new ClassReader(classBytes);
|
||||||
|
ClassWriter writer = new ClassWriter(0);
|
||||||
|
ClassVisitor mapper = new ClassVisitor(Opcodes.ASM9, writer) {
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
Entry e = classMap.get(className);
|
||||||
|
if (e != null) {
|
||||||
|
access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
|
||||||
|
access |= e.type.access;
|
||||||
|
}
|
||||||
|
if (fields.containsKey(className) || methods.containsKey(className) || mutations.containsKey(className)) { // make all classes with modifications public as well
|
||||||
|
access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
|
||||||
|
access |= Opcodes.ACC_PUBLIC;
|
||||||
|
}
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||||
|
Map<String, Entry> map = fields.get(className);
|
||||||
|
if (map != null) {
|
||||||
|
Entry e = map.get(name + descriptor);
|
||||||
|
if (e != null) {
|
||||||
|
access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); // remove all access modifiers
|
||||||
|
access |= e.type.access; // re-add the new one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((map = mutations.get(className)) != null) {
|
||||||
|
var e = map.get(name + descriptor);
|
||||||
|
if (e != null) {
|
||||||
|
access &= ~Opcodes.ACC_FINAL; // always AccessType.MUTABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visitField(access, name, descriptor, signature, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
Map<String, Entry> map = methods.get(className);
|
||||||
|
if (map != null) {
|
||||||
|
Entry e = map.get(name + descriptor);
|
||||||
|
if (e != null) {
|
||||||
|
access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
|
||||||
|
access |= e.type.access;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.accept(mapper, 0);
|
||||||
|
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
|
||||||
|
public enum AccessType {
|
||||||
|
ACCESSIBLE("accessible", Opcodes.ACC_PUBLIC),
|
||||||
|
EXTENDABLE("extendable", Opcodes.ACC_PROTECTED),
|
||||||
|
MUTABLE("mutable", ~Opcodes.ACC_FINAL);
|
||||||
|
private final String id;
|
||||||
|
private final int access;
|
||||||
|
|
||||||
|
public static AccessType of(String name) {
|
||||||
|
return Arrays.stream(values()).filter(a -> a.id.equals(name)).findFirst().orElseThrow(() -> new IllegalStateException("Unknown access type: " + name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private enum Access {
|
||||||
|
PUBLIC(1), PROTECTED(2), PACKAGE_PRIVATE(3), PRIVATE(4);
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
public static Access of(int access) {
|
||||||
|
if ((access & Opcodes.ACC_PUBLIC) != 0) {
|
||||||
|
return PUBLIC;
|
||||||
|
}
|
||||||
|
if ((access & Opcodes.ACC_PROTECTED) != 0) {
|
||||||
|
return PROTECTED;
|
||||||
|
}
|
||||||
|
if ((access & Opcodes.ACC_PRIVATE) != 0) {
|
||||||
|
return PRIVATE;
|
||||||
|
}
|
||||||
|
return PACKAGE_PRIVATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ public class MixinClassLoader extends URLClassLoader {
|
||||||
if (in == null)
|
if (in == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return NonsenseMixinService.getTransformer().transformClass(MixinEnvironment.getCurrentEnvironment(), name, in.readAllBytes());
|
return NonsenseMixinService.getTransformer().transformClass(MixinEnvironment.getCurrentEnvironment(), name, AccessWidener.processClass(in.readAllBytes(), binName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense.loader.impl.mixin;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.ModProperties;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.launch.AccessWidener;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.mod.BuiltinExtensions;
|
||||||
|
|
||||||
|
public class AWProcessor {
|
||||||
|
|
||||||
|
private static final String AW_EXTENSION_NAME = BuiltinExtensions.ACCESSWIDENER;
|
||||||
|
private static final Predicate<String> HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate();
|
||||||
|
private static final String SEPARATOR = "[\\t ]+";
|
||||||
|
|
||||||
|
public static void load(Collection<ModProperties> mods){
|
||||||
|
Map<String, AccessWidener.Entry> classMap = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, AccessWidener.Entry>> methods = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, AccessWidener.Entry>> fields = new ConcurrentHashMap<>();
|
||||||
|
Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>();
|
||||||
|
Set<String> classNames = new ConcurrentSkipListSet<>();
|
||||||
|
|
||||||
|
mods.stream().map(ModProperties::extensions).map(e -> (String) e.get(AW_EXTENSION_NAME))
|
||||||
|
.filter(Objects::nonNull).map(s -> "/" + s).map(AWProcessor.class::getResourceAsStream).filter(Objects::nonNull)
|
||||||
|
.map(InputStreamReader::new).map(BufferedReader::new).flatMap(BufferedReader::lines)
|
||||||
|
.map(l -> l.contains("#") ? l.split("#")[0] : l).filter(l -> !l.isBlank())
|
||||||
|
.filter(l -> !HEADER.test(l)).distinct()
|
||||||
|
.map(l -> l.replace("transitive-", "")) // ignore all transitive declarations (just make them normal) as they're only relevant for dev envs
|
||||||
|
.map(l -> l.split(SEPARATOR)).filter(l -> l.length > 0).map(AccessWidener.Entry::new).forEach(e -> {
|
||||||
|
classNames.add(e.className());
|
||||||
|
if ("class".equals(e.targetType())) {
|
||||||
|
if (e.type() == AccessWidener.AccessType.MUTABLE) {
|
||||||
|
throw new IllegalArgumentException("aw format error: classes can not have a 'mutable' modifier (at: " + e + ")");
|
||||||
|
}
|
||||||
|
if (!classMap.containsKey(e.className())) {
|
||||||
|
classMap.put(e.className(), e);
|
||||||
|
} else {
|
||||||
|
var other = classMap.get(e.className());
|
||||||
|
if (e.isAccessGreaterThan(other)) {
|
||||||
|
classMap.put(e.className(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("method".equals(e.targetType())) {
|
||||||
|
if (e.type() == AccessWidener.AccessType.MUTABLE) {
|
||||||
|
throw new IllegalArgumentException("aw format error: methods can not have a 'mutable' modifier (at: " + e + ")");
|
||||||
|
}
|
||||||
|
var map = methods.computeIfAbsent(e.className(), s -> new ConcurrentHashMap<>());
|
||||||
|
var id = e.name() + e.descriptor();
|
||||||
|
if (!map.containsKey(id)) {
|
||||||
|
map.put(id, e);
|
||||||
|
} else {
|
||||||
|
var other = map.get(id);
|
||||||
|
if (e.isAccessGreaterThan(other)) {
|
||||||
|
classMap.put(id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("field".equals(e.targetType())) {
|
||||||
|
if (e.type() == AccessWidener.AccessType.EXTENDABLE) {
|
||||||
|
throw new IllegalArgumentException("aw format error: fields can not have a 'extendable' modifier (at: " + e + ")");
|
||||||
|
}
|
||||||
|
var map = fields.computeIfAbsent(e.className(), s -> new ConcurrentHashMap<>());
|
||||||
|
var id = e.name() + e.descriptor();
|
||||||
|
if (e.type() == AccessWidener.AccessType.MUTABLE) {
|
||||||
|
mutations.computeIfAbsent(e.className(), s -> new ConcurrentHashMap<>()).putIfAbsent(id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map.containsKey(id)) {
|
||||||
|
map.put(id, e);
|
||||||
|
} else {
|
||||||
|
var other = map.get(id);
|
||||||
|
if (e.isAccessGreaterThan(other)) {
|
||||||
|
classMap.put(id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AccessWidener.load(new AccessWidener.Data(classMap, methods, fields, mutations, classNames));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.ecorous.esnesnon.nonsense.loader.impl.mod;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class BuiltinExtensions {
|
||||||
|
public final String MIXIN_CONFIG = "mixin_config";
|
||||||
|
public final String INCLUDED_JARS = "included_jars";
|
||||||
|
public final String PRE_LAUNCH = "pre_launch";
|
||||||
|
public final String ACCESSWIDENER = "frog_aw";
|
||||||
|
}
|
|
@ -1,8 +1,14 @@
|
||||||
package org.ecorous.esnesnon.nonsense.loader.impl.mod;
|
package org.ecorous.esnesnon.nonsense.loader.impl.mod;
|
||||||
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.api.mod.*;
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public record ModPropertiesImpl(String id, String name, SemVer version, License license,
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.ModDependencies;
|
||||||
ModCredits credits, ModDependencies dependencies,
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.ModExtensions;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.ModProperties;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.SemVer;
|
||||||
|
|
||||||
|
public record ModPropertiesImpl(String id, String name, SemVer version, String license,
|
||||||
|
Map<String, Collection<String>> credits, ModDependencies dependencies,
|
||||||
ModExtensions extensions) implements ModProperties {
|
ModExtensions extensions) implements ModProperties {
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,10 @@ public class ModPropertiesReader {
|
||||||
String version = config.get("frog.mod.version");
|
String version = config.get("frog.mod.version");
|
||||||
String license = config.get("frog.mod.license");
|
String license = config.get("frog.mod.license");
|
||||||
|
|
||||||
|
if (license == null){
|
||||||
|
license = "";
|
||||||
|
}
|
||||||
|
|
||||||
List<UnmodifiableConfig> creditsList = config.get("frog.mod.credits");
|
List<UnmodifiableConfig> creditsList = config.get("frog.mod.credits");
|
||||||
Map<String, Collection<String>> credits = new HashMap<>();
|
Map<String, Collection<String>> credits = new HashMap<>();
|
||||||
if (creditsList != null) {
|
if (creditsList != null) {
|
||||||
|
@ -68,8 +72,21 @@ public class ModPropertiesReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<ModDependencies.Entry> depends = config.get("frog.dependencies.depends");
|
Collection<ModDependencies.Entry> depends = config.get("frog.dependencies.depends");
|
||||||
|
if (depends == null){
|
||||||
|
depends = Collections.emptySet();
|
||||||
|
}
|
||||||
Collection<ModDependencies.Entry> breaks = config.get("frog.dependencies.breaks");
|
Collection<ModDependencies.Entry> breaks = config.get("frog.dependencies.breaks");
|
||||||
|
if (breaks == null){
|
||||||
|
breaks = Collections.emptySet();
|
||||||
|
}
|
||||||
Collection<ModDependencies.Entry> suggests = config.get("frog.dependencies.suggests");
|
Collection<ModDependencies.Entry> suggests = config.get("frog.dependencies.suggests");
|
||||||
|
if (suggests == null){
|
||||||
|
suggests = Collections.emptySet();
|
||||||
|
}
|
||||||
|
Collection<ModDependencies.Entry> provides = config.get("frog.dependencies.provides");
|
||||||
|
if (provides == null){
|
||||||
|
provides = Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
|
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
|
||||||
Map<String, Object> extensions = new HashMap<>();
|
Map<String, Object> extensions = new HashMap<>();
|
||||||
|
@ -77,7 +94,7 @@ public class ModPropertiesReader {
|
||||||
extensionsConfig.entrySet().forEach(entry -> extensions.put(entry.getKey(), entry.getValue()));
|
extensionsConfig.entrySet().forEach(entry -> extensions.put(entry.getKey(), entry.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModPropertiesImpl(id, name, SemVerImpl.parse(version), License.fromId(license), ModCredits.of(credits), new ModDependencies(depends, breaks, suggests), ModExtensions.of(extensions));
|
return new ModPropertiesImpl(id, name, SemVerImpl.parse(version), license, Collections.unmodifiableMap(credits), new ModDependencies(depends, breaks, suggests, provides), ModExtensions.of(extensions));
|
||||||
});
|
});
|
||||||
|
|
||||||
private final String version;
|
private final String version;
|
||||||
|
|
|
@ -1,18 +1,5 @@
|
||||||
package org.ecorous.esnesnon.nonsense.loader.impl.plugin.game.minecraft;
|
package org.ecorous.esnesnon.nonsense.loader.impl.plugin.game.minecraft;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.api.extensions.PreLaunchExtension;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.api.mod.*;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.Discovery;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.LoaderImpl;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.mod.ModPropertiesImpl;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.mod.ModPropertiesReader;
|
|
||||||
import org.ecorous.esnesnon.nonsense.loader.impl.plugin.NonsensePlugin;
|
|
||||||
import org.ecorous.esnesnon.nonsense_remapper.NonsenseRemapper;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.spongepowered.asm.mixin.Mixins;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
@ -23,19 +10,34 @@ import java.net.URL;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.api.extensions.PreLaunchExtension;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.api.mod.*;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.Discovery;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.LoaderImpl;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.mixin.AWProcessor;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.mod.BuiltinExtensions;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.mod.ModPropertiesImpl;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.mod.ModPropertiesReader;
|
||||||
|
import org.ecorous.esnesnon.nonsense.loader.impl.plugin.NonsensePlugin;
|
||||||
|
import org.ecorous.esnesnon.nonsense_remapper.NonsenseRemapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongepowered.asm.mixin.Mixins;
|
||||||
|
|
||||||
public class Minecraft implements NonsensePlugin {
|
public class Minecraft implements NonsensePlugin {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("Plugin/Minecraft");
|
private static final Logger LOGGER = LoggerFactory.getLogger("Plugin/Minecraft");
|
||||||
private static final String[] MINECRAFT_CLASSES = new String[]{
|
protected static final String[] MINECRAFT_CLASSES = new String[]{
|
||||||
"net/minecraft/client/main/Main.class",
|
"net/minecraft/client/main/Main.class",
|
||||||
"net/minecraft/client/MinecraftApplet.class",
|
"net/minecraft/client/MinecraftApplet.class",
|
||||||
"net/minecraft/server/Main.class"
|
"net/minecraft/server/Main.class"
|
||||||
};
|
};
|
||||||
|
|
||||||
private final List<ModProperties> modProperties = new ArrayList<>();
|
protected final List<ModProperties> modProperties = new ArrayList<>();
|
||||||
private String version;
|
private String version;
|
||||||
private Path gamePath;
|
protected Path gamePath;
|
||||||
private String foundMainClass;
|
protected String foundMainClass;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplicable() {
|
public boolean isApplicable() {
|
||||||
|
@ -45,9 +47,12 @@ public class Minecraft implements NonsensePlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(LoaderImpl loader) throws Exception {
|
public void init(LoaderImpl loader) throws Exception {
|
||||||
|
if (gamePath == null){
|
||||||
|
throw new IllegalStateException("Game not found yet!");
|
||||||
|
}
|
||||||
Path remappedGamePath = loader.getGameDir().resolve(".nonsense/remappedJars").resolve(version).resolve("game-" + version + "-remapped.jar");
|
Path remappedGamePath = loader.getGameDir().resolve(".nonsense/remappedJars").resolve(version).resolve("game-" + version + "-remapped.jar");
|
||||||
|
|
||||||
if (!Files.exists(remappedGamePath)){
|
if (!Files.exists(remappedGamePath.getParent())) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(remappedGamePath.getParent());
|
Files.createDirectories(remappedGamePath.getParent());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -56,16 +61,21 @@ public class Minecraft implements NonsensePlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
modProperties.add(new ModPropertiesImpl("minecraft", "Minecraft",
|
modProperties.add(new ModPropertiesImpl("minecraft", "Minecraft",
|
||||||
new MinecraftSemVerImpl(version), License.custom("MC-EULA", "Minecraft EULA"),
|
new MinecraftSemVerImpl(version), "MC-EULA",
|
||||||
ModCredits.of(Map.of("Mojang AB", Collections.singleton("Author"))),
|
Map.of("Mojang AB", Collections.singleton("Author")),
|
||||||
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
||||||
ModExtensions.of(Collections.emptyMap())));
|
ModExtensions.of(Collections.emptyMap())));
|
||||||
|
|
||||||
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
|
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
|
||||||
version.equals(path.getFileName().toString()), path ->
|
version.equals(path.getFileName().toString()), path ->
|
||||||
path.getFileName().toString().endsWith(LoaderImpl.MOD_FILE_EXTENSION));
|
path.getFileName().toString().endsWith(LoaderImpl.MOD_FILE_EXTENSION));
|
||||||
// TODO add mods found on the classpath
|
|
||||||
Collection<URL> classpathMods = this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).distinct().toList();
|
Collection<URL> classpathMods = this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).distinct().toList();
|
||||||
|
|
||||||
|
classpathMods.parallelStream().map(ModPropertiesReader::readFile).forEachOrdered(modProperties::add);
|
||||||
|
for (Path mod : new HashSet<>(mods)) {
|
||||||
|
findJiJMods(mod, mods);
|
||||||
|
}
|
||||||
|
|
||||||
mods.parallelStream().map(Path::toUri).map(uri -> {
|
mods.parallelStream().map(Path::toUri).map(uri -> {
|
||||||
try {
|
try {
|
||||||
return uri.toURL();
|
return uri.toURL();
|
||||||
|
@ -74,28 +84,45 @@ public class Minecraft implements NonsensePlugin {
|
||||||
}
|
}
|
||||||
}).forEachOrdered(LoaderImpl.getInstance().getClassloader()::addURL);
|
}).forEachOrdered(LoaderImpl.getInstance().getClassloader()::addURL);
|
||||||
|
|
||||||
classpathMods.parallelStream().map(ModPropertiesReader::readFile).forEachOrdered(modProperties::add);
|
// TODO respect mod dependencies and display errors appropriately
|
||||||
mods.parallelStream().map(ModPropertiesReader::read).forEachOrdered(opt -> opt.ifPresent(modProperties::add));
|
|
||||||
|
|
||||||
modProperties.parallelStream().forEach(props -> {
|
modProperties.parallelStream().forEach(props -> {
|
||||||
String name = props.extensions().get("mixin_config");
|
String name = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
Mixins.addConfiguration(name);
|
Mixins.addConfiguration(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Files.exists(remappedGamePath) && !loader.isDevelopment()){
|
if (!loader.isDevelopment()) {
|
||||||
try {
|
if (!Files.exists(remappedGamePath)) {
|
||||||
NonsenseRemapper.run(version, gamePath, remappedGamePath, true, false);
|
NonsenseRemapper.run(version, gamePath, remappedGamePath, true, false);
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
AWProcessor.load(modProperties);
|
||||||
LoaderImpl.getInstance().getClassloader().addURL(remappedGamePath.toUri().toURL());
|
|
||||||
} catch (Throwable e) {
|
var runtimePath = loader.isDevelopment() ? gamePath : remappedGamePath;
|
||||||
throw new RuntimeException(e);
|
LoaderImpl.getInstance().getClassloader().addURL(runtimePath.toUri().toURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void findJiJMods(Path mod, Collection<Path> mods) throws IOException {
|
||||||
|
Optional<ModProperties> opt = ModPropertiesReader.read(mod);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
ModProperties p = opt.get();
|
||||||
|
modProperties.add(p);
|
||||||
|
List<List<Map<String, String>>> entries = p.extensions().getOrDefault(BuiltinExtensions.INCLUDED_JARS, Collections.emptyList());
|
||||||
|
if (entries.isEmpty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(mod)){
|
||||||
|
for (var jars : entries) {
|
||||||
|
for (Map<String, String> jar : jars) {
|
||||||
|
Path path = fs.getPath(jar.get("path"));
|
||||||
|
mods.add(path);
|
||||||
|
findJiJMods(path, mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +131,7 @@ public class Minecraft implements NonsensePlugin {
|
||||||
return modProperties;
|
return modProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path findGame() {
|
protected Path findGame() {
|
||||||
LOGGER.info("Locating game..");
|
LOGGER.info("Locating game..");
|
||||||
String jar = System.getProperty("nonsense.plugin.minecraft.gameJar");
|
String jar = System.getProperty("nonsense.plugin.minecraft.gameJar");
|
||||||
if (jar != null) {
|
if (jar != null) {
|
||||||
|
@ -124,7 +151,7 @@ public class Minecraft implements NonsensePlugin {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkLocation(Path jar){
|
protected boolean checkLocation(Path jar) {
|
||||||
if (!Files.exists(jar) || Files.isDirectory(jar)) {
|
if (!Files.exists(jar) || Files.isDirectory(jar)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue