feat: reworked to make it extension-based, done?

might just work, but it's untested for now so take care
This commit is contained in:
zaaarf 2024-02-15 00:54:12 +01:00
parent e50d61850b
commit a6aa21a985
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
5 changed files with 162 additions and 130 deletions

View file

@ -1,4 +1,4 @@
# WIP, IT DOES NOT WORK YET! # UNTESTED FOR NOW, TAKE CARE!
# Lillero-mixin # Lillero-mixin
Lillero-mixin is a Mixin plugin capable of applying [Lillero](https://github.com/zaaarf/lillero) ASM patches without Lillero-mixin is a Mixin plugin capable of applying [Lillero](https://github.com/zaaarf/lillero) ASM patches without
@ -6,6 +6,10 @@ needing to inject itself as a JAR library. While slightly dirtier code-wise, thi
[Lillero-loader](https://github.com/zaaarf/lillero-loader) of being compatible with both Forge and Fabric - and, barring [Lillero-loader](https://github.com/zaaarf/lillero-loader) of being compatible with both Forge and Fabric - and, barring
major API changes, with any other future mod loader that will try to force Mixin on you. major API changes, with any other future mod loader that will try to force Mixin on you.
To use this, write a class extending `LilleroMixinPlugin`. Then, register it as a Mixin plugin; it usually involves editing
or writing your mod's configuration, but the exact steps vary depending on your mod loader.
Specific instructions for Fabric and Forge coming as soon as I have time for this.
## Credits ## Credits
This time there's one other project that must be mentioned. I would've never thought of this had I not stumbled on This time there's one other project that must be mentioned. I would've never thought of this had I not stumbled on
[Manningham Mills](https://github.com/Chocohead/Fabric-ASM). So, thanks to Chocohead for showing that it was indeed [Manningham Mills](https://github.com/Chocohead/Fabric-ASM). So, thanks to Chocohead for showing that it was indeed

View file

@ -25,9 +25,9 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.apache.logging.log4j:log4j-api:2.17.1' implementation 'org.apache.logging.log4j:log4j-api:2.22.1'
implementation 'org.apache.logging.log4j:log4j-core:2.17.1' implementation 'org.apache.logging.log4j:log4j-core:2.22.1'
implementation 'org.ow2.asm:asm-commons:9.4' implementation 'org.ow2.asm:asm-commons:9.6'
implementation 'ftbsc:lll:0.5.0' implementation 'ftbsc:lll:0.5.0'
implementation 'org.spongepowered:mixin:0.8.3' implementation 'org.spongepowered:mixin:0.8.5'
} }

View file

@ -1,124 +0,0 @@
package ftbsc.lll.mixin;
import ftbsc.lll.IInjector;
import ftbsc.lll.exceptions.InjectionException;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.*;
public class LilleroMixin implements IMixinConfigPlugin {
private final Map<String, List<IInjector>> injectorMap = new HashMap<>();
/**
* Called after the plugin is instantiated, do any setup here.
* @param mixinPackage The mixin root package from the config
*/
@Override
public void onLoad(String mixinPackage) {
for (IInjector inj : ServiceLoader.load(IInjector.class, this.getClass().getClassLoader())) {
//LOGGER.info(RESOURCE, "Registering injector {}", inj.name());
List<IInjector> injectors = this.injectorMap.get(inj.targetClass());
if(injectors == null) {
injectors = new ArrayList<>();
injectorMap.put(inj.targetClass(), injectors);
}
injectors.add(inj);
}
}
/**
* Called only if the "referenceMap" key in the config is <b>not</b> set.
* This allows the refmap file name to be supplied by the plugin
* programatically if desired. Returning <code>null</code> will revert to
* the default behaviour of using the default refmap json file.
*
* @return Path to the refmap resource or null to revert to the default
*/
@Override //TODO ?
public String getRefMapperConfig() {
return null;
}
/**
* Called during mixin intialisation, allows this plugin to control whether
* a specific will be applied to the specified target. Returning false will
* remove the target from the mixin's target set, and if all targets are
* removed then the mixin will not be applied at all.
*
* @param targetClassName Fully qualified class name of the target class
* @param mixinClassName Fully qualified class name of the mixin
* @return True to allow the mixin to be applied, or false to remove it from
* target's mixin set
*/
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}
/**
* Called after all configurations are initialised, this allows this plugin
* to observe classes targetted by other mixin configs and optionally remove
* targets from its own set. The set myTargets is a direct view of the
* targets collection in this companion config and keys may be removed from
* this set to suppress mixins in this config which target the specified
* class. Adding keys to the set will have no effect.
*
* @param myTargets Target class set from the companion config
* @param otherTargets Target class set incorporating targets from all other
* configs, read-only
*/
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
/**
* After mixins specified in the configuration have been processed, this
* method is called to allow the plugin to add any additional mixins to
* load. It should return a list of mixin class names or return null if the
* plugin does not wish to append any mixins of its own.
*
* @return additional mixins to apply
*/
@Override
public List<String> getMixins() {
return null;
}
/**
* Called immediately <b>before</b> a mixin is applied to a target class,
* allows any pre-application transformations to be applied.
*
* @param targetClassName Transformed name of the target class
* @param targetClass Target class tree
* @param mixinClassName Name of the mixin class
* @param mixinInfo Information about this mixin
*/
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
injectorMap.get(targetClassName).forEach((inj) -> targetClass.methods.stream()
.filter(m -> m.name.equals(inj.methodName()) && m.desc.equals(inj.methodDesc()))
.forEach(m -> {
try {
inj.inject(targetClass, m);
} catch (InjectionException ignored) {} //TODO log
}));
}
/**
* Called immediately <b>after</b> a mixin is applied to a target class,
* allows any post-application transformations to be applied.
*
* @param targetClassName Transformed name of the target class
* @param targetClass Target class tree
* @param mixinClassName Name of the mixin class
* @param mixinInfo Information about this mixin
*/
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}

View file

@ -0,0 +1,152 @@
package ftbsc.lll.mixin;
import ftbsc.lll.IInjector;
import ftbsc.lll.exceptions.InjectionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.*;
/**
* Allows you to load your mod's Lillero patches as a Mixin plugin.
* Extend this class and specify the child as a plugin in your mod's Mixin
* config. Refer to your mod loader's instructions for details on how this
* is done.
* Methods are left non-final in case you want to alter their behaviour in
* any way, but I can't really see any merit in doing so.
*/
public abstract class LilleroMixinPlugin implements IMixinConfigPlugin {
/**
* The {@link Logger} used by this library.
*/
protected static final Logger LOGGER = LogManager.getLogger(LilleroMixinPlugin.class);
/**
* Maps each fully-qualified name to its associated class.
*/
private final Map<String, List<IInjector>> injectorMap = new HashMap<>();
/**
* Whether Lillero should take precedence over regular mixins.
*/
private final boolean precedence;
/**
* The constructor.
* @param precedence whether Lillero should take precedence over regular mixins
*/
public LilleroMixinPlugin(boolean precedence) {
this.precedence = precedence;
}
/**
* Called after the plugin is instantiated, do any setup here.
* @param mixinPackage The mixin root package from the config
*/
@Override
public void onLoad(String mixinPackage) {
for(IInjector inj : ServiceLoader.load(IInjector.class, this.getClass().getClassLoader())) {
LOGGER.info("Registering injector {}", inj.name());
List<IInjector> injectors = this.injectorMap.get(inj.targetClass());
if(injectors == null) {
injectors = new ArrayList<>();
injectorMap.put(inj.targetClass(), injectors);
}
injectors.add(inj);
}
}
/**
* Returns null, so it's effectively ignored.
* @return always null
*/
@Override
public String getRefMapperConfig() {
return null;
}
/**
* Tells Mixin to always apply these patches.
* Lillero doesn't support conditional patches: any check should happen
* within the patch code itself, with the patch code's scope.
* @param targetClassName fully qualified class name of the target class
* @param mixinClassName fully qualified class name of the mixin
* @return always true
*/
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}
/**
* Does nothing, as we don't need to alter the target class list.
* @param myTargets target class set from the companion config
* @param otherTargets target class set incorporating targets from all other
* configs, read-only
*/
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}
/**
* This does not apply any additional mixins.
* @return always null
*/
@Override
public List<String> getMixins() {
return null;
}
/**
* Called immediately before a mixin is applied to a target class.
* Will apply Lillero patches if {@link #precedence} is true.
* @param className transformed name of the target class
* @param clazz target class tree
* @param mixinClassName name of the mixin class
* @param mixinInfo information about this mixin
*/
@Override
public void preApply(String className, ClassNode clazz, String mixinClassName, IMixinInfo mixinInfo) {
if(precedence) this.applyLilleroPatches(className, clazz);
}
/**
* Called immediately after a mixin is applied to a target class.
* Will apply Lillero patches if {@link #precedence} is false.
* @param className transformed name of the target class
* @param clazz target class tree
* @param mixinClassName name of the mixin class
* @param mixinInfo information about this mixin
*/
@Override
public void postApply(String className, ClassNode clazz, String mixinClassName, IMixinInfo mixinInfo) {
if(!precedence) this.applyLilleroPatches(className, clazz);
}
/**
* Applies the appropriate Lillero patches given a node and a class name.
* @param className the class' fully qualified name
* @param clazz the target class
*/
protected void applyLilleroPatches(String className, ClassNode clazz) {
List<IInjector> injectors = this.injectorMap.remove(className); // remove so it's only once
if(injectors != null) {
injectors.forEach((inj) -> clazz.methods.stream()
.filter(m -> m.name.equals(inj.methodName()) && m.desc.equals(inj.methodDesc()))
.forEach(m -> {
try {
LOGGER.info(
"Patching {}.{} with {} ({})",
className, m.name,
inj.name(),
inj.reason());
inj.inject(clazz, m);
} catch (InjectionException exception) {
LOGGER.error("Error applying patch '{}' : {}", inj.name(), exception);
}
}));
}
}
}

View file

@ -1 +1 @@
ftbsc.lll.mixin.LilleroMixin ftbsc.lll.mixin.LilleroMixinPlugin