diff --git a/build.gradle b/build.gradle index dd860f35..5726a77c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,19 @@ buildscript { repositories { - jcenter() - maven { url = "https://files.minecraftforge.net/maven" } + maven { url = 'https://files.minecraftforge.net/maven' } + maven { url = 'https://repo.spongepowered.org/maven' } } dependencies { classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT' } } + apply plugin: 'net.minecraftforge.gradle.forge' +apply plugin: 'org.spongepowered.mixin' //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. - -version = "1.1" +version = "2.0" group = "zone.rong.loliasm" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "loliasm" @@ -45,12 +47,27 @@ repositories { jcenter() + maven { + url "https://repo.spongepowered.org/maven" + } + } dependencies { embed 'me.nallar.whocalled:WhoCalled:1.1' + // implementation ("org.spongepowered:mixin:0.8.3-SNAPSHOT") { + embed ("org.spongepowered:mixin:0.8") { + exclude module: "asm-commons" + exclude module: "asm-tree" + exclude module: "launchwrapper" + exclude module: "guava" + exclude module: "log4j-core" + exclude module: "gson" + exclude module: "commons-io" + } + } processResources { @@ -72,6 +89,12 @@ processResources { } } +sourceSets { + main { + ext.refMap = "mixins.loliasm.refmap.json" + } +} + jar { from (configurations.embed.collect { it.isDirectory() ? it : zipTree(it) }) { exclude 'LICENSE.txt', 'META-INF/MANIFSET.MF', 'META-INF/maven/**', 'META-INF/*.RSA', 'META-INF/*.SF' @@ -80,7 +103,9 @@ jar { attributes([ "FMLCorePluginContainsFMLMod": true, "FMLCorePlugin": 'zone.rong.loliasm.LoliLoadingPlugin', - "ForceLoadAsMod": true + "ForceLoadAsMod": true, + "TweakClass": 'org.spongepowered.asm.launch.MixinTweaker', + "FMLAT": "loliasm_at.cfg" ]) } } diff --git a/changelog.md b/changelog.md new file mode 100644 index 00000000..cae67a94 --- /dev/null +++ b/changelog.md @@ -0,0 +1,21 @@ +# LoliASM Changelog + +## 2.0 +- Revamped configuration. Some new options there. **REFRESH YOUR CONFIGS** +- Removed soft/hard patch variations for optimizing BakedQuads. Hard patch remains as it is now stable and saves more RAM. +- Implemented 'cleanupLaunchClassLoader' - saves lots of memory in the `LaunchClassLoader` caching things relating to class transformation/loading. *Foamfix* does this already to some fields but I've done it on more fields. +- Implemented 'remapperMemorySaver' - saves lots of memory in `FMLDeobfuscatingRemapper` by deduplicating Strings as well as not caching non-Minecraft/Forge classes/fields/methods. +- Implemented 'optimizeDataStructures' - optimizes structures around Minecraft. This will be updated nearly every version if I find any places that gives tangible results. +- Implemented 'optimizeFurnaceRecipes' - optimizes tick time when searching for FurnaceRecipes. By Hashing recipes and queries are only a hash lookup now rather than a loop => if match => return. +- Starting to implement object canonization, or deduplication as Foamfix calls it, hopefully it will match Foamfix and beat it out. We'll see. +- Starting to implement BlockStateContainer, StateImplementation memory squashers. +- Added mixins to do some of the leg work for me as I'm too lazy to write ASM all the time. +- Cleaned up `LoliReflector`, potentially an API candidate. +- Relocated some coremod classes. + +## 1.1 +- Fixed issues in some cases (first found in Thaumcraft) where redirecting BakedQuad::new calls would fail because of stackframe issues. + +## 1.0 +- First release. +- Optimizations on BakedQuads, soft/hard patch variants. \ No newline at end of file diff --git a/src/main/java/com/google/common/collect/LoliEntrySet.java b/src/main/java/com/google/common/collect/LoliEntrySet.java new file mode 100644 index 00000000..e6a16afb --- /dev/null +++ b/src/main/java/com/google/common/collect/LoliEntrySet.java @@ -0,0 +1,38 @@ +package com.google.common.collect; + +import javax.annotation.Nullable; +import java.util.Map; + +public class LoliEntrySet extends ImmutableSet>> { + + private final Object viewedState; + + public LoliEntrySet(Object viewedState) { + this.viewedState = viewedState; + } + + @Override + public UnmodifiableIterator>> iterator() { + return new LoliIterator<>(i -> (Map.Entry>) LoliImmutableMap.stateAndIndex.getBy(viewedState, i), size()); + } + + @Override + public int size() { + return LoliImmutableMap.numProperties.applyAsInt(viewedState); + } + + @Override + public boolean contains(@Nullable Object key) { + if (!(key instanceof Map.Entry)) { + return false; + } + Map.Entry entry = (Map.Entry) key; + Object valueInMap = LoliImmutableMap.stateAndKey.getBy(viewedState, entry.getKey()); + return valueInMap != null && valueInMap.equals(entry.getValue()); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/src/main/java/com/google/common/collect/LoliImmutableMap.java b/src/main/java/com/google/common/collect/LoliImmutableMap.java new file mode 100644 index 00000000..377cfe61 --- /dev/null +++ b/src/main/java/com/google/common/collect/LoliImmutableMap.java @@ -0,0 +1,46 @@ +package com.google.common.collect; + +import zone.rong.loliasm.api.datastructures.fastmap.StateAndIndex; +import zone.rong.loliasm.api.datastructures.fastmap.StateAndKey; +import zone.rong.loliasm.api.datastructures.fastmap.FastMapStateHolder; + +import javax.annotation.Nullable; +import java.util.function.ToIntFunction; + +public class LoliImmutableMap extends ImmutableMap> { + + public static ToIntFunction numProperties; + public static StateAndKey stateAndKey; + public static StateAndIndex stateAndIndex; + + private final FastMapStateHolder viewedState; + + public LoliImmutableMap(FastMapStateHolder viewedState) { + this.viewedState = viewedState; + } + + @Override + public int size() { + return numProperties.applyAsInt(viewedState); + } + + @Override + public Comparable get(@Nullable Object key) { + return stateAndKey.getBy(viewedState, key); + } + + @Override + public ImmutableSet>> createEntrySet() { + return new LoliEntrySet<>(viewedState); + } + + @Override + public ImmutableSet>> entrySet() { + return new LoliEntrySet<>(viewedState); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/src/main/java/com/google/common/collect/LoliIterator.java b/src/main/java/com/google/common/collect/LoliIterator.java new file mode 100644 index 00000000..4ded16c6 --- /dev/null +++ b/src/main/java/com/google/common/collect/LoliIterator.java @@ -0,0 +1,29 @@ +package com.google.common.collect; + +import java.util.Map; +import java.util.function.IntFunction; + +public class LoliIterator extends UnmodifiableIterator>>{ + + private final IntFunction>> getIth; + private final int length; + + private int currentIndex; + + public LoliIterator(IntFunction>> getIth, int length) { + this.getIth = getIth; + this.length = length; + } + + @Override + public boolean hasNext() { + return currentIndex < length; + } + + @Override + public Map.Entry> next() { + Map.Entry> next = getIth.apply(currentIndex); + ++currentIndex; + return next; + } +} \ No newline at end of file diff --git a/src/main/java/zone/rong/loliasm/LoliASM.java b/src/main/java/zone/rong/loliasm/LoliASM.java new file mode 100644 index 00000000..fb603110 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/LoliASM.java @@ -0,0 +1,63 @@ +package zone.rong.loliasm; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IReloadableResourceManager; +import net.minecraft.util.HttpUtil; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import zone.rong.loliasm.api.mixins.RegistrySimpleExtender; +import zone.rong.loliasm.client.models.MultipartBakedModelCache; +import zone.rong.loliasm.client.models.conditions.CanonicalConditions; + +import java.util.ArrayList; +import java.util.List; + +@Mod(modid = "loliasm", name = "LoliASM", version = "1.2") +@Mod.EventBusSubscriber +public class LoliASM { + + public static List simpleRegistryInstances = new ArrayList<>(); + + /* + private static Deduplicator deduplicator; + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onModelBake(ModelBakeEvent event) { + if (deduplicator == null) { + deduplicator = new Deduplicator(); + } + IRegistry bakedModels = event.getModelRegistry(); + Set keys = bakedModels.getKeys(); + ProgressManager.ProgressBar bar = ProgressManager.push("LoliASM: Optimizing IBakedModels", keys.size()); + Stopwatch stopwatch = Stopwatch.createStarted(); + for (ModelResourceLocation mrl : keys) { + bar.step(mrl.toString()); + deduplicator.deduplicate(bakedModels.getObject(mrl)); + } + LoliLogger.instance.info("It took {} to optimize IBakedModels", stopwatch.stop()); + } + */ + + @Mod.EventHandler + @SuppressWarnings("deprecation") + public void preInit(FMLPreInitializationEvent event) { + ((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(manager -> { + CanonicalConditions.destroyCache(); + MultipartBakedModelCache.destroyCache(); + }); + } + + @Mod.EventHandler + public void loadComplete(FMLLoadCompleteEvent event) { + // Map cache = (Map) LoliReflector.resolveFieldGetter(ModelLoaderRegistry.class, "cache").invokeExact(); + // ProgressManager.ProgressBar bar = ProgressManager.push("Optimizing Models", cache.size(), true); + // deduplicator = null; // Free the deduplicator + LoliLogger.instance.info("Trimming simple registries"); + HttpUtil.DOWNLOADER_EXECUTOR.execute(() -> { + simpleRegistryInstances.forEach(RegistrySimpleExtender::trim); + simpleRegistryInstances = null; + }); + } + +} diff --git a/src/main/java/zone/rong/loliasm/LoliConfig.java b/src/main/java/zone/rong/loliasm/LoliConfig.java index bc8eebd8..367c7f51 100644 --- a/src/main/java/zone/rong/loliasm/LoliConfig.java +++ b/src/main/java/zone/rong/loliasm/LoliConfig.java @@ -7,66 +7,100 @@ import net.minecraft.launchwrapper.Launch; import net.minecraftforge.common.config.Config; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; public class LoliConfig { - public static Data config; + private static Data config; public static class Data { @Config.Ignore - final String softPatchComment = "Not so invasive patches, this will work 99% of the time in a large modpack."; - public final boolean softPatch; + final String bakedQuadsSquasherComment = "Saves memory by optimizing BakedQuads with creation of new BakedQuad classes to squash variables down."; + final boolean bakedQuadsSquasher; @Config.Ignore - final String hardPatchComment = "Super invasive patches, EXPERIMENTAL."; - public final boolean hardPatch; + final String logClassesThatNeedPatchingComment = "Turn this on to log any callers using old BakedQuads constructors. Check logs and put them in the 'bakedQuadPatchClasses' list."; + public final boolean logClassesThatNeedPatching; @Config.Ignore - final String logClassesThatNeedPatchingComment = "Turn this on to log any callers using deprecated BakedQuad constructors. If you do see any classes, put them in the list below."; - public final boolean logClassesThatNeedPatching; + final String bakedQuadPatchClassesComment = "List any classes using old BakedQuad constructors. 'logClassesThatNeedPatching' is crucial in identifying the classes."; + public final String[] bakedQuadPatchClasses; + + @Config.Ignore + final String cleanupLaunchClassLoaderComment = "Experimental: Saves memory from cleaning out redundant maps and collections in Mojang's LaunchClassLoader."; + public final boolean cleanupLaunchClassLoader; + + @Config.Ignore + final String remapperMemorySaverComment = "Experimental: Saves memory by canonizing strings cached in the remapper. May impact loading time by a small amount."; + public final boolean remapperMemorySaver; @Config.Ignore - final String hardPatchClassesComment = "Classes that needs their BakedQuad::new calls redirected. In layman terms, if you see a crash, grab the class name, and slot it here..."; - public final String[] hardPatchClasses; + final String canonizeObjectsComment = "Experimental: Saves memory by pooling different Object instances and deduplicating them from different locations such as ResourceLocations, IBakedModels."; + public final boolean canonizeObjects; - public Data(boolean softPatch, boolean hardPatch, boolean logClassesThatNeedPatching, String[] hardPatchClasses) { - this.softPatch = softPatch; - this.hardPatch = hardPatch; + @Config.Ignore + final String optimizeDataStructuresComment = "Saves memory by optimizing various data structures around Minecraft, MinecraftForge and mods."; + public final boolean optimizeDataStructures; + + @Config.Ignore + final String optimizeFurnaceRecipesComment = "Saves memory and furnace recipe search time by optimizing FurnaceRecipes' algorithm."; + public final boolean optimizeFurnaceRecipes; + + public Data(boolean bakedQuadsSquasher, + boolean logClassesThatNeedPatching, + String[] bakedQuadPatchClasses, + boolean cleanupLaunchClassLoader, + boolean remapperMemorySaver, + boolean canonizeObjects, + boolean optimizeDataStructures, + boolean optimizeFurnaceRecipes) { + this.bakedQuadsSquasher = bakedQuadsSquasher; this.logClassesThatNeedPatching = logClassesThatNeedPatching; - this.hardPatchClasses = hardPatchClasses; + this.bakedQuadPatchClasses = bakedQuadPatchClasses; + this.cleanupLaunchClassLoader = cleanupLaunchClassLoader; + this.remapperMemorySaver = remapperMemorySaver; + this.canonizeObjects = canonizeObjects; + this.optimizeDataStructures = optimizeDataStructures; + this.optimizeFurnaceRecipes = optimizeFurnaceRecipes; } } - public static Data initConfig() throws IOException { + public static Data getConfig() { + if (config != null) { + return config; + } Gson gson = new GsonBuilder() + .disableHtmlEscaping() .setPrettyPrinting() .addDeserializationExclusionStrategy(new ExclusionStrategy() { @Override public boolean shouldSkipField(FieldAttributes f) { return f.getAnnotation(Config.Ignore.class) != null; } - @Override public boolean shouldSkipClass(Class clazz) { return false; } }) .create(); - Data data; + Data data = null; File configFile = new File(Launch.minecraftHome, "config/loliasm.json"); if (!configFile.exists()) { - try (FileWriter writer = new FileWriter(configFile)) { - data = new Data(true, false, false, new String[] { "net.minecraft.client.renderer.block.model.FaceBakery" }); - gson.toJson(data, writer); + try { + configFile.createNewFile(); + try (FileWriter writer = new FileWriter(configFile)) { + data = config = new Data(true, true, new String[] { "net.minecraft.client.renderer.block.model.FaceBakery" }, true, true, true, true, true); + gson.toJson(data, writer); + } + } catch (IOException e) { + e.printStackTrace(); } } else { try (FileReader reader = new FileReader(configFile)) { data = config = gson.fromJson(reader, Data.class); + } catch (IOException e) { + e.printStackTrace(); } } return data; diff --git a/src/main/java/zone/rong/loliasm/LoliLoadingPlugin.java b/src/main/java/zone/rong/loliasm/LoliLoadingPlugin.java deleted file mode 100644 index 63289d5e..00000000 --- a/src/main/java/zone/rong/loliasm/LoliLoadingPlugin.java +++ /dev/null @@ -1,35 +0,0 @@ -package zone.rong.loliasm; - -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; - -import java.util.Map; - -@IFMLLoadingPlugin.MCVersion(ForgeVersion.mcVersion) -@IFMLLoadingPlugin.Name("LoliASM") -public class LoliLoadingPlugin implements IFMLLoadingPlugin { - - @Override - public String[] getASMTransformerClass() { - return null; - } - - @Override - public String getModContainerClass() { - return null; - } - - @Override - public String getSetupClass() { - return null; - } - - @Override - public void injectData(Map data) { } - - @Override - public String getAccessTransformerClass() { - return "zone.rong.loliasm.LoliTransformer"; - } - -} \ No newline at end of file diff --git a/src/main/java/zone/rong/loliasm/LoliLogger.java b/src/main/java/zone/rong/loliasm/LoliLogger.java index 09262b6c..c8fca83b 100644 --- a/src/main/java/zone/rong/loliasm/LoliLogger.java +++ b/src/main/java/zone/rong/loliasm/LoliLogger.java @@ -5,6 +5,6 @@ public class LoliLogger { - public static Logger instance = LogManager.getLogger("LoliASM"); + public static final Logger instance = LogManager.getLogger("LoliASM"); } diff --git a/src/main/java/zone/rong/loliasm/LoliReflector.java b/src/main/java/zone/rong/loliasm/LoliReflector.java index 4e0939fa..a2434da6 100644 --- a/src/main/java/zone/rong/loliasm/LoliReflector.java +++ b/src/main/java/zone/rong/loliasm/LoliReflector.java @@ -1,7 +1,14 @@ package zone.rong.loliasm; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import net.minecraftforge.fml.relauncher.ReflectionHelper; +import org.objectweb.asm.Type; +import zone.rong.loliasm.api.datastructures.fastmap.StateAndIndex; +import zone.rong.loliasm.api.datastructures.fastmap.StateAndKey; +import zone.rong.loliasm.api.datastructures.fastmap.FastMapStateHolder; +import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; @@ -15,6 +22,42 @@ public class LoliReflector { private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + private static final MethodHandle classLoader$DefineClass = resolveMethod(ClassLoader.class, "defineClass", String.class, byte[].class, int.class, int.class); + + static { + ClassLoader classLoader = ImmutableMap.class.getClassLoader(); + // defineClass(classLoader, LoliEntrySet.class); + // defineClass(classLoader, LoliImmutableMap.class); + // defineClass(classLoader, LoliIterator.class); + defineClass(classLoader, StateAndIndex.class); + defineClass(classLoader, StateAndKey.class); + defineClass(classLoader, FastMapStateHolder.class); + } + + public static Class defineClass(CL classLoader, Class clazz) { + String name = Type.getInternalName(clazz); + InputStream byteStream = clazz.getResourceAsStream('/' + name + ".class"); + try { + byte[] classBytes = new byte[byteStream.available()]; + final int bytesRead = byteStream.read(classBytes); + Preconditions.checkState(bytesRead == classBytes.length); + // return defineClass(classLoader, name.replace('/', '.'), classBytes); + return (Class) classLoader$DefineClass.invoke(classLoader, name.replace('/', '.'), classBytes, 0, classBytes.length); + } catch (Throwable e) { + e.printStackTrace(); + } + return clazz; + } + + public static Class defineClass(CL classLoader, String name, byte[] classBytes) { + try { + return (Class) classLoader$DefineClass.invokeExact(classLoader, name, classBytes, 0, classBytes.length); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + return null; + } + public static MethodHandle resolveCtor(Class clazz, Class... args) { try { Constructor ctor = clazz.getDeclaredConstructor(args); @@ -56,7 +99,7 @@ public static MethodHandle resolveFieldGetter(Class clazz, String fieldName) if (!field.isAccessible()) { field.setAccessible(true); } - return MethodHandles.lookup().unreflectGetter(field); + return lookup.unreflectGetter(field); } catch (IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); return null; @@ -69,7 +112,7 @@ public static MethodHandle resolveFieldSetter(Class clazz, String fieldName) if (!field.isAccessible()) { field.setAccessible(true); } - return MethodHandles.lookup().unreflectSetter(field); + return lookup.unreflectSetter(field); } catch (IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); return null; @@ -78,7 +121,7 @@ public static MethodHandle resolveFieldSetter(Class clazz, String fieldName) public static MethodHandle resolveFieldGetter(Class clazz, String fieldName, String obfFieldName) { try { - return MethodHandles.lookup().unreflectGetter(ReflectionHelper.findField(clazz, fieldName, obfFieldName)); + return lookup.unreflectGetter(ReflectionHelper.findField(clazz, fieldName, obfFieldName)); } catch (IllegalAccessException e) { e.printStackTrace(); return null; @@ -87,11 +130,10 @@ public static MethodHandle resolveFieldGetter(Class clazz, String fieldName, public static MethodHandle resolveFieldSetter(Class clazz, String fieldName, String obfFieldName) { try { - return MethodHandles.lookup().unreflectSetter(ReflectionHelper.findField(clazz, fieldName, obfFieldName)); + return lookup.unreflectSetter(ReflectionHelper.findField(clazz, fieldName, obfFieldName)); } catch (IllegalAccessException e) { e.printStackTrace(); return null; } } - } diff --git a/src/main/java/zone/rong/loliasm/LoliTransformer.java b/src/main/java/zone/rong/loliasm/LoliTransformer.java deleted file mode 100644 index a8e5dab9..00000000 --- a/src/main/java/zone/rong/loliasm/LoliTransformer.java +++ /dev/null @@ -1,254 +0,0 @@ -package zone.rong.loliasm; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.launchwrapper.IClassTransformer; -import net.minecraft.launchwrapper.Launch; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.tree.*; -import zone.rong.loliasm.patches.*; - -import java.io.IOException; -import java.util.ListIterator; -import java.util.Map; -import java.util.function.Function; - -import static org.objectweb.asm.Opcodes.*; - -public class LoliTransformer implements IClassTransformer { - - public static final boolean isDeobf = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); - - Map> transformations; - - public LoliTransformer() throws IOException { - LoliLogger.instance.info("The lolis are now preparing to bytecode manipulate your game."); - LoliConfig.Data data = LoliConfig.initConfig(); - if (data.softPatch) { - transformations = new Object2ObjectOpenHashMap<>(2); - addTransformation("net.minecraft.client.renderer.block.model.BakedQuad", this::optimize$BakedQuad); - addTransformation("net.minecraft.client.renderer.block.model.BakedQuadRetextured", this::fixReferences$BakedQuadRetextured); - } else if (data.hardPatch) { - transformations = new Object2ObjectOpenHashMap<>(5 + data.hardPatchClasses.length); - addTransformation("net.minecraft.client.renderer.block.model.BakedQuad", BakedQuadPatch::rewriteBakedQuad); - addTransformation("net.minecraft.client.renderer.block.model.BakedQuadRetextured", BakedQuadRetexturedPatch::patchBakedQuadRetextured); - addTransformation("net.minecraftforge.client.model.pipeline.UnpackedBakedQuad", UnpackedBakedQuadPatch::rewriteUnpackedBakedQuad); - addTransformation("net.minecraftforge.client.model.pipeline.UnpackedBakedQuad$Builder", UnpackedBakedQuadPatch::rewriteUnpackedBakedQuad$Builder); - addTransformation("zone.rong.loliasm.bakedquad.BakedQuadFactory", BakedQuadFactoryPatch::patchCreateMethod); - for (String classToPatch : data.hardPatchClasses) { - // addTransformation(classToPatch, this::redirectNewBakedQuadCalls); - addTransformation(classToPatch, this::redirectNewBakedQuadCalls$EventBased); - } - } - } - - public void addTransformation(String key, Function value) { - LoliLogger.instance.info("Adding class {} to the transformation queue", key); - transformations.put(key, value); - } - - @Override - public byte[] transform(String name, String transformedName, byte[] bytes) { - if (transformations == null) { - return bytes; - } - Function getBytes = transformations.get(transformedName); - if (getBytes != null) { - return getBytes.apply(bytes); - } - return bytes; - } - - // Better way - private byte[] redirectNewBakedQuadCalls$EventBased(byte[] bytes) { - ClassReader reader = new ClassReader(bytes); - - ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); // in certain cases, this flag is needed - - MethodInsnNode node = new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/bakedquad/BakedQuadFactory", "create", "([IILnet/minecraft/util/EnumFacing;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;ZLnet/minecraft/client/renderer/vertex/VertexFormat;)Lnet/minecraft/client/renderer/block/model/BakedQuad;", false); - - reader.accept(new RedirectNewWithStaticCallClassVisitor(writer, "net/minecraft/client/renderer/block/model/BakedQuad", node), 0); - - return writer.toByteArray(); - } - - @Deprecated - private byte[] redirectNewBakedQuadCalls(byte[] bytes) { - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - reader.accept(node, 0); - - final String creationDesc = "([IILnet/minecraft/util/EnumFacing;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;ZLnet/minecraft/client/renderer/vertex/VertexFormat;)Lnet/minecraft/client/renderer/block/model/BakedQuad;"; - - for (MethodNode method : node.methods) { - ListIterator iter = method.instructions.iterator(); - while (iter.hasNext()) { - AbstractInsnNode instruction = iter.next(); - if (instruction instanceof TypeInsnNode) { - TypeInsnNode newInstruction = (TypeInsnNode) instruction; - if (newInstruction.desc.equals("net/minecraft/client/renderer/block/model/BakedQuad")) { - iter.remove(); // Remove NEW - iter.next(); - iter.remove(); // Remove DUP - } - } - if (instruction instanceof MethodInsnNode && instruction.getOpcode() == INVOKESPECIAL) { - MethodInsnNode methodInstruction = (MethodInsnNode) instruction; - if (methodInstruction.name.equals("") && methodInstruction.owner.equals("net/minecraft/client/renderer/block/model/BakedQuad")) { - MethodInsnNode replaceInstruction = new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/bakedquad/BakedQuadFactory", "create", creationDesc, false); - // Replace call to BakedQuad::new, with EfficientBakedQuadsFactory::create - iter.remove(); - iter.add(replaceInstruction); - } - } - } - } - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - node.accept(writer); - return writer.toByteArray(); - } - - private byte[] optimize$BakedQuad(byte[] bytes) { - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - reader.accept(node, 0); - - final String tintIndex = isDeobf ? "tintIndex" : "field_178213_b"; - final String ctor = ""; - final String hasTintIndex = isDeobf ? "hasTintIndex" : "func_178212_b"; - final String getTintIndex = isDeobf ? "getTintIndex" : "func_178211_c"; - - // Transform tintIndex int field -> byte field - for (FieldNode field : node.fields) { - if (field.name.equals(tintIndex)) { - field.desc = "B"; - break; - } - } - - for (MethodNode method : node.methods) { - if (method.access == 0x1 && method.name.equals(ctor)) { - ListIterator iterator = method.instructions.iterator(); - while (iterator.hasNext()) { - AbstractInsnNode instruction = iterator.next(); - if (instruction.getOpcode() == PUTFIELD) { - FieldInsnNode fieldInstruction = (FieldInsnNode) instruction; - if (fieldInstruction.name.equals(tintIndex)) { - method.instructions.insertBefore(instruction, new InsnNode(I2B)); - fieldInstruction.desc = "B"; - break; - } - } - } - } else if (method.name.equals(hasTintIndex) || method.name.equals(getTintIndex)) { - ListIterator iterator = method.instructions.iterator(); - while (iterator.hasNext()) { - AbstractInsnNode instruction = iterator.next(); - if (instruction.getOpcode() == GETFIELD) { - FieldInsnNode fieldInstruction = (FieldInsnNode) instruction; - if (fieldInstruction.name.equals(tintIndex)) { - fieldInstruction.desc = "B"; - break; - } - } - } - } - } - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - node.accept(writer); - return writer.toByteArray(); - } - - private byte[] fixReferences$BakedQuadRetextured(byte[] bytes) { - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - reader.accept(node, 0); - - final String tintIndex = isDeobf ? "tintIndex" : "field_178213_b"; - final String ctor = ""; - // final String applyDiffuseLighting = "applyDiffuseLighting"; // forge-added, no-deobf. - - for (MethodNode method : node.methods) { - if (method.access == 0x1 && method.name.equals(ctor)) { - ListIterator iterator = method.instructions.iterator(); - while (iterator.hasNext()) { - AbstractInsnNode instruction = iterator.next(); - if (instruction.getOpcode() == GETFIELD) { - FieldInsnNode fieldInstruction = (FieldInsnNode) instruction; - if (fieldInstruction.name.equals(tintIndex)) { - fieldInstruction.desc = "B"; - break; - } - } - } - } - } - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - node.accept(writer); - return writer.toByteArray(); - } - - static class RedirectNewWithStaticCallClassVisitor extends ClassVisitor { - - final String ownerOfNewCall; - final MethodInsnNode staticRedirect; - - RedirectNewWithStaticCallClassVisitor(ClassVisitor cv, String ownerOfNewCall, MethodInsnNode staticRedirect) { - super(ASM5, cv); - this.ownerOfNewCall = ownerOfNewCall; - this.staticRedirect = staticRedirect; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return new ReplaceNewWithStaticCall(ASM5, cv.visitMethod(access, name, desc, signature, exceptions), ownerOfNewCall, staticRedirect); - } - } - - static class ReplaceNewWithStaticCall extends MethodVisitor { - - final String ownerOfNewCall; - final MethodInsnNode staticRedirect; - - boolean foundNewCall = false; - - ReplaceNewWithStaticCall(int api, MethodVisitor mv, String ownerOfNewCall, MethodInsnNode staticRedirect) { - super(api, mv); - this.ownerOfNewCall = ownerOfNewCall; - this.staticRedirect = staticRedirect; - } - - @Override - public void visitTypeInsn(int opcode, String type) { - if (opcode == NEW && type.equals(ownerOfNewCall)) { - foundNewCall = true; - } else { - super.visitTypeInsn(opcode, type); - } - } - - @Override - public void visitInsn(int opcode) { - if (foundNewCall && opcode == DUP) { - foundNewCall = false; - } else { - super.visitInsn(opcode); - } - } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - if (opcode == INVOKESPECIAL && owner.equals(ownerOfNewCall) && name.equals("")) { - super.visitMethodInsn(staticRedirect.getOpcode(), staticRedirect.owner, staticRedirect.name, staticRedirect.desc, staticRedirect.itf); - } else { - super.visitMethodInsn(opcode, owner, name, desc, itf); - } - } - } - -} diff --git a/src/main/java/zone/rong/loliasm/api/HashingStrategies.java b/src/main/java/zone/rong/loliasm/api/HashingStrategies.java index a2276619..a397446d 100644 --- a/src/main/java/zone/rong/loliasm/api/HashingStrategies.java +++ b/src/main/java/zone/rong/loliasm/api/HashingStrategies.java @@ -3,6 +3,7 @@ import it.unimi.dsi.fastutil.Hash; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemTransformVec3f; +import net.minecraft.item.ItemStack; import java.util.Arrays; import java.util.Objects; @@ -70,4 +71,21 @@ private static int vectorHash(ItemTransformVec3f vector) { return hash * 31 + ((Float.floatToIntBits(vector.translation.getX())) * 31 + Float.floatToIntBits(vector.translation.getY())) * 31 + Float.floatToIntBits(vector.translation.getZ()); } + public static final Hash.Strategy ITEM_AND_META_HASH = new Hash.Strategy() { + @Override + public int hashCode(ItemStack o) { + int hash = 1; + hash = hash * 31 + o.getItem().hashCode(); + int metadata = o.getMetadata(); + return (metadata == Short.MAX_VALUE || metadata == 0) ? hash : hash * 31 + metadata; + } + @Override + public boolean equals(ItemStack a, ItemStack b) { + if (a == null || b == null) { + return false; + } + return a.getItem() == b.getItem() && (a.getMetadata() == Short.MAX_VALUE || b.getMetadata() == Short.MAX_VALUE || a.getMetadata() == b.getMetadata()); + } + }; + } diff --git a/src/main/java/zone/rong/loliasm/api/StringPool.java b/src/main/java/zone/rong/loliasm/api/StringPool.java new file mode 100644 index 00000000..372a2bb2 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/StringPool.java @@ -0,0 +1,24 @@ +package zone.rong.loliasm.api; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Locale; + +@SuppressWarnings("unused") +public class StringPool { + + private static final ObjectOpenHashSet POOL = new ObjectOpenHashSet<>(4096); + + public static String canonize(String string) { + synchronized (POOL) { + return POOL.addOrGet(string); + } + } + + public static String lowerCaseAndCanonize(String string) { + synchronized (POOL) { + return POOL.addOrGet(string.toLowerCase(Locale.ROOT)); + } + } + +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/DummyMap.java b/src/main/java/zone/rong/loliasm/api/datastructures/DummyMap.java new file mode 100644 index 00000000..069355d4 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/DummyMap.java @@ -0,0 +1,38 @@ +package zone.rong.loliasm.api.datastructures; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Used as a ImmutableMap-like structure, but with the methods not throwing UnsupportedOperationException + */ +public class DummyMap implements Map { + + private static final DummyMap INSTANCE = new DummyMap<>(); + private static final Set SET_INSTANCE = Collections.newSetFromMap(INSTANCE); + + @SuppressWarnings("unchecked") + public static DummyMap of() { + return (DummyMap) INSTANCE; + } + + @SuppressWarnings("unchecked") + public static Set asSet() { + return (Set) SET_INSTANCE; + } + + public int size() { return 0; } + public boolean isEmpty() { return true; } + public boolean containsKey(Object key) { return false; } + public boolean containsValue(Object value) { return false; } + public V get(Object key) { return null; } + public V put(K key, V value) { return value; } + public V remove(Object key) { return null; } + public void putAll(Map m) { } + public void clear() { } + public Set keySet() { return Collections.emptySet(); } + public Collection values() { return Collections.emptySet(); } + public Set entrySet() { return Collections.emptySet(); } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/ImmutableArrayCollection.java b/src/main/java/zone/rong/loliasm/api/datastructures/ImmutableArrayCollection.java new file mode 100644 index 00000000..d7dc27ad --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/ImmutableArrayCollection.java @@ -0,0 +1,125 @@ +package zone.rong.loliasm.api.datastructures; + +import com.google.common.collect.Iterators; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +/** + * Super slim ImmutableList structure + */ +public class ImmutableArrayCollection implements Collection { + + private final T[] array; + + public ImmutableArrayCollection(T[] array, boolean clone) { + this.array = clone ? array.clone() : array; + } + + @Override + public int size() { + return array.length; + } + + @Override + public boolean isEmpty() { + return array.length == 0; + } + + @Override + public boolean contains(Object o) { + if (o == null) { + for (T t : array) { + if (t == null) { + return true; + } + } + } else { + for (T t : array) { + if (t.equals(o)) { + return true; + } + } + } + return false; + } + + @Override + public Iterator iterator() { + return Iterators.forArray(this.array); + } + + @Override + public Object[] toArray() { + return array.clone(); + } + + @Override + public T1[] toArray(T1[] dst) { + T[] src = this.array; + if (dst.length < src.length) { + return (T1[]) Arrays.copyOf(src, src.length, dst.getClass()); + } + System.arraycopy(src, 0, dst, 0, src.length); + if (dst.length > src.length) { + dst[src.length] = null; + } + return dst; + } + + @Override + public boolean add(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + for (T t : array) { + if (!t.equals(o)) { + return false; + } + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Object[]) { + return Arrays.equals(array, (Object[]) o); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(array); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingArrayMap.java b/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingArrayMap.java new file mode 100644 index 00000000..a9ad27e5 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingArrayMap.java @@ -0,0 +1,25 @@ +package zone.rong.loliasm.api.datastructures.canonical; + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import zone.rong.loliasm.api.StringPool; + +import java.util.Map; + +// TODO: Hook Deduplicator's pools +public class AutoCanonizingArrayMap extends Object2ObjectArrayMap { + + public AutoCanonizingArrayMap(Map map) { + super(map); + } + + @Override + public V put(K k, V v) { + if (k instanceof String) { + k = (K) StringPool.canonize((String) k); + } + if (v instanceof String) { + v = (V) StringPool.canonize((String) v); + } + return super.put(k, v); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingSet.java b/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingSet.java new file mode 100644 index 00000000..ae161063 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/canonical/AutoCanonizingSet.java @@ -0,0 +1,22 @@ +package zone.rong.loliasm.api.datastructures.canonical; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import zone.rong.loliasm.api.StringPool; + +import java.util.Set; + +// TODO: Hook Deduplicator's pools +public class AutoCanonizingSet extends ObjectOpenHashSet { + + public AutoCanonizingSet(Set set) { + super(set); + } + + @Override + public boolean add(K k) { + if (k instanceof String) { + k = (K) StringPool.canonize((String) k); + } + return super.add(k); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/deobf/DeobfuscatedMappingsMap.java b/src/main/java/zone/rong/loliasm/api/datastructures/deobf/DeobfuscatedMappingsMap.java new file mode 100644 index 00000000..81c9bb3e --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/deobf/DeobfuscatedMappingsMap.java @@ -0,0 +1,60 @@ +package zone.rong.loliasm.api.datastructures.deobf; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import zone.rong.loliasm.api.datastructures.DummyMap; +import zone.rong.loliasm.api.datastructures.canonical.AutoCanonizingArrayMap; +import zone.rong.loliasm.api.StringPool; + +import java.util.Map; + +/** + * This is a special Map structure that replaces the raw and cached mappings. + * + * - Anything class that is a Minecraft or MinecraftForge class passes the check and is placed in the map + * - Anything that has its inner maps containing String values that matches any SRG mapping is placed in the map + */ +public class DeobfuscatedMappingsMap extends Object2ObjectOpenHashMap> { + + // TODO: Move to deduplicator + private static final ObjectOpenHashSet> innerMapCanonicalCache = new ObjectOpenHashSet<>(); + + // Typed as this for the MethodHandle + public static Map> of(Map> startingMap, boolean isField) { + return new DeobfuscatedMappingsMap(startingMap, isField); + } + + private final String prefix; + + DeobfuscatedMappingsMap(Map> startingMap, boolean isField) { + super(startingMap); + this.prefix = isField ? "field_" : "func_"; + } + + @Override + public Map put(String s, Map innerMap) { + if (s.indexOf('/') == -1 || s.startsWith("net/minecraft")) { // If it is a Minecraft or MinecraftForge class, we add to the map (short circuiting operation to not check innerMap strings) + return super.put(StringPool.canonize(s), innerMapCanonicalCache.addOrGet(new AutoCanonizingArrayMap<>(innerMap))); + } else if (innerMap.isEmpty()) { // If there are no methods or fields mapped to 's' class, we return null and add nothing in the map + return innerMap; + } else if (innerMap.values().stream().anyMatch(string -> string.startsWith(prefix))) { + return super.put(StringPool.canonize(s), innerMapCanonicalCache.addOrGet(new AutoCanonizingArrayMap<>(innerMap))); // Check if any of the values start with 'func_' or 'field_', indicating that these 99% are mappings + } + /* + for (Map.Entry entry : innerMap.entrySet()) { + // Check if any of the values start with 'func_' or 'field_', indicating that these 99% are mappings + if (Stream.of("func_", "field_").anyMatch(entry.getValue()::startsWith)) { + return super.put(s, innerMap); + } + } + */ + // If all of this fails, return innerMap. + return innerMap; + } + + @Override + public Map get(Object k) { + Map get = super.get(k); + return get == null ? DummyMap.of() : get; // Return DummyMap instead of Collections.emptyMap to not throw UnsupportedOperationException when Map#put + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/deobf/FieldDescriptionsMap.java b/src/main/java/zone/rong/loliasm/api/datastructures/deobf/FieldDescriptionsMap.java new file mode 100644 index 00000000..87123789 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/deobf/FieldDescriptionsMap.java @@ -0,0 +1,31 @@ +package zone.rong.loliasm.api.datastructures.deobf; + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import zone.rong.loliasm.api.datastructures.canonical.AutoCanonizingArrayMap; +import zone.rong.loliasm.api.StringPool; + +import java.util.Map; + +/** + * This replaces the fieldDescriptions map in the remapper, it canonizes the inner maps and the inner maps' strings + */ +public class FieldDescriptionsMap extends Object2ObjectOpenHashMap> { + + // TODO: Move to deduplicator + private static final ObjectOpenHashSet> innerMapCanonicalCache = new ObjectOpenHashSet<>(); + + public FieldDescriptionsMap(Map> startingMap) { + super(startingMap); + } + + @Override + public Map put(String s, Map innerMap) { + s = StringPool.canonize(s); + if (!(innerMap instanceof Object2ObjectArrayMap)) { + innerMap = innerMapCanonicalCache.addOrGet(new AutoCanonizingArrayMap<>(innerMap)); + } + return super.put(s, innerMap); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMap.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMap.java new file mode 100644 index 00000000..545e8c2b --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMap.java @@ -0,0 +1,141 @@ +package zone.rong.loliasm.api.datastructures.fastmap; + +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import zone.rong.loliasm.api.datastructures.fastmap.key.BinaryFastMapKey; +import zone.rong.loliasm.api.datastructures.fastmap.key.CompactFastMapKey; +import zone.rong.loliasm.api.datastructures.fastmap.key.FastMapKey; + +import javax.annotation.Nullable; +import java.util.*; + +public class FastMap { + + private final IProperty[] props; + private final FastMapKey[] keys; + private final IBlockState[] states; + + public FastMap(Collection> properties, Map, Comparable>, IBlockState> valuesMap, boolean compact) { + int size = properties.size(); + List> keys = new ArrayList<>(size); + List> props = new ArrayList<>(size); + int factorUpTo = 1; + for (IProperty prop : properties) { + props.add(prop); + FastMapKey nextKey; + if (compact) { + nextKey = new CompactFastMapKey<>(prop, factorUpTo); + } else { + nextKey = new BinaryFastMapKey<>(prop, factorUpTo); + } + keys.add(nextKey); + factorUpTo *= nextKey.getFactorToNext(); + } + this.props = props.toArray(new IProperty[0]); + this.keys = keys.toArray(new FastMapKey[0]); + List valuesList = new ArrayList<>(factorUpTo); + for (int i = 0; i < factorUpTo; ++i) { + valuesList.add(null); + } + for (Map.Entry, Comparable>, IBlockState> state : valuesMap.entrySet()) { + valuesList.set(getIndexOf(state.getKey()), state.getValue()); + } + this.states = valuesList.toArray(new IBlockState[0]); + } + + /** + * Computes the value for a neighbor state + * + * @param oldIndex The original state index + * @param prop The property to be replaced + * @param value The new value of this property + * @return The value corresponding to the specified neighbor, or null if value is not a valid value for prop + */ + @Nullable + public > IBlockState with(int oldIndex, IProperty prop, T value) { + final FastMapKey keyToChange = getKeyFor(prop); + if (keyToChange == null) { + return null; + } + int newIndex = keyToChange.replaceIn(oldIndex, value); + if (newIndex < 0) { + return null; + } + return states[newIndex]; + } + + /** + * @return The map index corresponding to the given property-value assignment + */ + public int getIndexOf(Map, Comparable> state) { + int id = 0; + for (FastMapKey k : keys) { + id += k.toPartialMapIndex(state.get(k.getProperty())); + } + return id; + } + + /** + * Returns the value assigned to a property at a given map index + * + * @param stateIndex The map index for the assignment to check + * @param property The property to retrieve + * @return The value of the property or null if the state if not present + */ + @Nullable + public > T getValue(int stateIndex, IProperty property) { + final FastMapKey propId = getKeyFor(property); + if (propId == null) { + return null; + } + return propId.getValue(stateIndex); + } + + /** + * Returns the given property and its value in the given state + * + * @param propertyIndex The index of the property to retrieve + * @param stateIndex The index of the state to use for the value + */ + public Map.Entry, Comparable> getEntry(int propertyIndex, int stateIndex) { + FastMapKey key = getKey(propertyIndex); + return new AbstractMap.SimpleImmutableEntry<>(key.getProperty(), key.getValue(stateIndex)); + } + + /** + * Same as {@link FastMap#with(int, IProperty, Comparable)}, but usable when the type of the value to set is not + * correctly typed + */ + public > IBlockState withUnsafe(int globalTableIndex, IProperty property, Object newV) { + final FastMapKey keyToChange = getKeyFor(property); + if (keyToChange == null) { + return null; + } + int newIndex = keyToChange.replaceIn(globalTableIndex, (T) newV); + if (newIndex < 0) { + return null; + } + return states[newIndex]; + } + + public int numProperties() { + return keys.length; + } + + > FastMapKey getKey(int keyIndex) { + return (FastMapKey) keys[keyIndex]; + } + + @Nullable + @SuppressWarnings("unchecked") + private > FastMapKey getKeyFor(IProperty prop) { + int index = -1; + for (int i = 0; i < props.length; i++) { + IProperty p = props[i]; + if (p.equals(prop)) { + return (FastMapKey) keys[i]; + } + } + return null; + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMapStateHolder.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMapStateHolder.java new file mode 100644 index 00000000..cb78b2f7 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/FastMapStateHolder.java @@ -0,0 +1,20 @@ +package zone.rong.loliasm.api.datastructures.fastmap; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.block.properties.IProperty; + +public interface FastMapStateHolder { + + FastMap getStateMap(); + + void setStateMap(FastMap newValue); + + int getStateIndex(); + + void setStateIndex(int newValue); + + ImmutableMap, Comparable> getVanillaPropertyMap(); + + void replacePropertyMap(ImmutableMap, Comparable> newMap); + +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/PropertyIndexer.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/PropertyIndexer.java new file mode 100644 index 00000000..3ea6ec59 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/PropertyIndexer.java @@ -0,0 +1,228 @@ +package zone.rong.loliasm.api.datastructures.fastmap; + +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.IStringSerializable; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; + +/** + * Provides a way of converting between values of a property and indices in [0, #values). Most properties are covered + * by one of the (faster) specific implementations, all other properties use the {@link GenericIndexer} + * + * Rongmario: changed GenericIndexer to only back a single T[] array instead of a T[] and a Map + * this has a speed penalty but not by much. + */ +public abstract class PropertyIndexer> { + + private static final Map, PropertyIndexer> KNOWN_INDEXERS = new Reference2ObjectOpenHashMap<>(); + + private final IProperty property; + private final int numValues; + + @SuppressWarnings("unchecked") + public static > PropertyIndexer makeIndexer(IProperty prop) { + PropertyIndexer unchecked = KNOWN_INDEXERS.computeIfAbsent(prop, propInner -> { + PropertyIndexer result = null; + if (propInner instanceof PropertyBool) { + result = new BoolIndexer((PropertyBool) propInner); + } else if (propInner instanceof PropertyInteger) { + result = new IntIndexer((PropertyInteger) propInner); + } else if (WeirdVanillaEnumFacingIndexer.isApplicable(propInner)) { + result = new WeirdVanillaEnumFacingIndexer((IProperty) propInner); + } else if (propInner instanceof PropertyEnum) { + result = new EnumIndexer<>((PropertyEnum) propInner); + } + if (result == null || !result.isValid()) { + return new GenericIndexer<>(propInner); + } else { + return result; + } + }); + return (PropertyIndexer) unchecked; + } + + PropertyIndexer(IProperty property) { + this.property = property; + this.numValues = property.getAllowedValues().size(); + } + + public IProperty getProperty() { + return property; + } + + public int numValues() { + return numValues; + } + + @Nullable + public abstract T byIndex(int index); + + public abstract int toIndex(T value); + + /** + * Checks if this indexer is valid, i.e. iterates over the correct set of values in the correct order + */ + protected boolean isValid() { + Collection allowed = getProperty().getAllowedValues(); + int index = 0; + for (T val : allowed) { + if (toIndex(val) != index || !val.equals(byIndex(index))) { + return false; + } + ++index; + } + return true; + } + + private static class BoolIndexer extends PropertyIndexer { + + BoolIndexer(PropertyBool property) { + super(property); + } + + @Override + @Nullable + public Boolean byIndex(int index) { + switch (index) { + case 0: + return Boolean.TRUE; + case 1: + return Boolean.FALSE; + default: + return null; + } + } + + @Override + public int toIndex(Boolean value) { + return value ? 0 : 1; + } + } + + private static class IntIndexer extends PropertyIndexer { + private final int min; + + IntIndexer(PropertyInteger property) { + super(property); + this.min = property.getAllowedValues().stream().min(Comparator.naturalOrder()).orElse(0); + } + + @Override + @Nullable + public Integer byIndex(int index) { + if (index >= 0 && index < numValues()) { + return index + min; + } else { + return null; + } + } + + @Override + public int toIndex(Integer value) { + return value - min; + } + } + + private static class EnumIndexer & IStringSerializable> extends PropertyIndexer { + + private final int ordinalOffset; + private final E[] enumValues; + + EnumIndexer(PropertyEnum property) { + super(property); + this.ordinalOffset = property.getAllowedValues().stream().mapToInt(Enum::ordinal).min().orElse(0); + this.enumValues = property.getValueClass().getEnumConstants(); + } + + @Override + @Nullable + public E byIndex(int index) { + final int arrayIndex = index + ordinalOffset; + if (arrayIndex < enumValues.length) { + return enumValues[arrayIndex]; + } else { + return null; + } + } + + @Override + public int toIndex(E value) { + return value.ordinal() - ordinalOffset; + } + } + + /** + * This is a kind of hack for a vanilla quirk: BlockStateProperties.FACING (which is used everywhere) has the order + * NORTH, EAST, SOUTH, WEST, UP, DOWN + * instead of the "canonical" order given by the enum + */ + private static class WeirdVanillaEnumFacingIndexer extends PropertyIndexer { + + private static final EnumFacing[] ORDER = EnumFacing.values(); + + static boolean isApplicable(IProperty prop) { + Collection values = prop.getAllowedValues(); + if (values.size() != ORDER.length) { + return false; + } + return Arrays.equals(ORDER, values.toArray()); + } + + WeirdVanillaEnumFacingIndexer(IProperty prop) { + super(prop); + Preconditions.checkState(isValid()); + } + + @Override + @Nullable + public EnumFacing byIndex(int index) { + if (index >= 0 && index < ORDER.length) { + return ORDER[index]; + } else { + return null; + } + } + + @Override + public int toIndex(EnumFacing value) { + return value.ordinal(); + } + } + + private static class GenericIndexer> extends PropertyIndexer { + + private final T[] values; + + GenericIndexer(IProperty property) { + super(property); + this.values = (T[]) property.getAllowedValues().toArray(); + } + + @Override + @Nullable + public T byIndex(int index) { + return values[index]; + } + + @Override + public int toIndex(T value) { + for (int i = 0; i < values.length; i++) { + T v = values[i]; + if (v.equals(value)) { + return i; + } + } + return -1; + } + } +} \ No newline at end of file diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndIndex.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndIndex.java new file mode 100644 index 00000000..a39afcbe --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndIndex.java @@ -0,0 +1,9 @@ +package zone.rong.loliasm.api.datastructures.fastmap; + +import java.util.Map; + +public interface StateAndIndex { + + Map.Entry> getBy(Object state, int index); + +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndKey.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndKey.java new file mode 100644 index 00000000..0bd66c61 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/StateAndKey.java @@ -0,0 +1,8 @@ +package zone.rong.loliasm.api.datastructures.fastmap; + +@FunctionalInterface +public interface StateAndKey { + + Comparable getBy(Object state, Object key); + +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/BinaryFastMapKey.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/BinaryFastMapKey.java new file mode 100644 index 00000000..85570e7d --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/BinaryFastMapKey.java @@ -0,0 +1,59 @@ +package zone.rong.loliasm.api.datastructures.fastmap.key; + +import com.google.common.base.Preconditions; +import net.minecraft.block.properties.IProperty; +import net.minecraft.util.math.MathHelper; + +/** + * A bitmask-based implementation of a FastMapKey. This reduces the density of data in the value matrix, but allows + * accessing values with only some bitwise operations, which are much faster than integer division + */ +public class BinaryFastMapKey> extends FastMapKey { + + private final byte firstBitInValue; + private final byte firstBitAfterValue; + + public BinaryFastMapKey(IProperty property, int mapFactor) { + super(property); + Preconditions.checkArgument(mapFactor != 0 && (mapFactor & mapFactor - 1) == 0); // MathHelper#isPowerOfTwo + int numValues = numValues(); + final int addedFactor = MathHelper.smallestEncompassingPowerOfTwo(numValues); + Preconditions.checkState(numValues <= addedFactor); + Preconditions.checkState(addedFactor < 2 * numValues); + final int setBitInBaseFactor = MathHelper.log2(mapFactor); + final int setBitInAddedFactor = MathHelper.log2(addedFactor); + Preconditions.checkState(setBitInBaseFactor + setBitInAddedFactor <= 31); + firstBitInValue = (byte) setBitInBaseFactor; + firstBitAfterValue = (byte) (setBitInBaseFactor + setBitInAddedFactor); + } + + @Override + public T getValue(int mapIndex) { + final int clearAbove = mapIndex & lowestNBits(firstBitAfterValue); + return byInternalIndex(clearAbove >>> firstBitInValue); + } + + @Override + public int replaceIn(int mapIndex, T newValue) { + final int keepMask = ~lowestNBits(firstBitAfterValue) | lowestNBits(firstBitInValue); + return (keepMask & mapIndex) | toPartialMapIndex(newValue); + } + + @Override + public int toPartialMapIndex(Comparable value) { + return getInternalIndex(value) << firstBitInValue; + } + + @Override + public int getFactorToNext() { + return 1 << (firstBitAfterValue - firstBitInValue); + } + + private int lowestNBits(byte n) { + if (n >= Integer.SIZE) { + return -1; + } else { + return (1 << n) - 1; + } + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/CompactFastMapKey.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/CompactFastMapKey.java new file mode 100644 index 00000000..63ca64b2 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/CompactFastMapKey.java @@ -0,0 +1,46 @@ +package zone.rong.loliasm.api.datastructures.fastmap.key; + +import net.minecraft.block.properties.IProperty; + +/** + * A "compact" implementation of a FastMapKey, i.e. one which completely fills the value matrix + */ +public class CompactFastMapKey> extends FastMapKey { + + private final int mapFactor; + + public CompactFastMapKey(IProperty property, int mapFactor) { + super(property); + this.mapFactor = mapFactor; + } + + @Override + public T getValue(int mapIndex) { + int index = (mapIndex / mapFactor) % numValues(); + return byInternalIndex(index); + } + + @Override + public int replaceIn(int mapIndex, T newValue) { + final int lowerData = mapIndex % mapFactor; + int numValues = numValues(); + final int upperFactor = mapFactor * numValues; + final int upperData = mapIndex - mapIndex % upperFactor; + int internalIndex = getInternalIndex(newValue); + if (internalIndex < 0 || internalIndex >= numValues) { + return -1; + } else { + return lowerData + mapFactor * internalIndex + upperData; + } + } + + @Override + public int toPartialMapIndex(Comparable value) { + return mapFactor * getInternalIndex(value); + } + + @Override + public int getFactorToNext() { + return numValues(); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/FastMapKey.java b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/FastMapKey.java new file mode 100644 index 00000000..4a53e557 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/datastructures/fastmap/key/FastMapKey.java @@ -0,0 +1,60 @@ +package zone.rong.loliasm.api.datastructures.fastmap.key; + +import net.minecraft.block.properties.IProperty; +import zone.rong.loliasm.api.datastructures.fastmap.PropertyIndexer; + +/** + * Defines the indexing strategy for a single property in a FastMap + */ +public abstract class FastMapKey> { + /** + * Maps values of the property to indices in [0, numValues()) and vice versa + */ + private final PropertyIndexer indexer; + + protected FastMapKey(IProperty property) { + this.indexer = PropertyIndexer.makeIndexer(property); + } + + /** + * @param mapIndex An index in the FastMap's value matrix + * @return The value of this property in that index + */ + public abstract T getValue(int mapIndex); + + /** + * @param mapIndex The original index in the FastMap's value matrix + * @param newValue The value to assign to this property + * @return The index in the value matrix corresponding to the input state with only the value of this property + * replaced by newValue + */ + public abstract int replaceIn(int mapIndex, T newValue); + + /** + * @param value A possible value of this property + * @return An integer such that the sum over the returned values for all properties is the state corresponding to + * the arguments + */ + public abstract int toPartialMapIndex(Comparable value); + + /** + * @return An integer such that adding multiples of this value does not change the result of getValue + */ + public abstract int getFactorToNext(); + + public final int numValues() { + return indexer.numValues(); + } + + public final IProperty getProperty() { + return indexer.getProperty(); + } + + public final int getInternalIndex(Comparable value) { + return indexer.toIndex((T) value); + } + + public final T byInternalIndex(int internalIndex) { + return indexer.byIndex(internalIndex); + } +} diff --git a/src/main/java/zone/rong/loliasm/api/mixins/RegistrySimpleExtender.java b/src/main/java/zone/rong/loliasm/api/mixins/RegistrySimpleExtender.java new file mode 100644 index 00000000..568e0968 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/api/mixins/RegistrySimpleExtender.java @@ -0,0 +1,9 @@ +package zone.rong.loliasm.api.mixins; + +public interface RegistrySimpleExtender { + + void clearUnderlyingMap(); + + void trim(); + +} diff --git a/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadClassFactory.java b/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadClassFactory.java index 5cc83308..6575dea1 100644 --- a/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadClassFactory.java +++ b/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadClassFactory.java @@ -6,9 +6,8 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import zone.rong.loliasm.LoliReflector; -import zone.rong.loliasm.LoliTransformer; +import zone.rong.loliasm.core.LoliTransformer; -import java.lang.invoke.MethodHandle; import java.util.Locale; import static org.objectweb.asm.Opcodes.*; @@ -21,8 +20,6 @@ */ public final class BakedQuadClassFactory { - private static final MethodHandle classLoader$DefineClass = LoliReflector.resolveMethod(ClassLoader.class, "defineClass", String.class, byte[].class, int.class, int.class); - // Called prior to transforming BakedQuadFactory public static void predefineBakedQuadClasses() { for (String face : new String[] { "Down", "Up", "North", "South", "West", "East" }) { @@ -270,7 +267,7 @@ public static void predefineBakedQuadClasses() { // // Solution found: // No need to COMPUTE_FRAMES! - Class clazz = (Class) classLoader$DefineClass.invokeExact((ClassLoader) Launch.classLoader, className.replace('/', '.'), classBytes, 0, classBytes.length); + Class clazz = LoliReflector.defineClass((ClassLoader) Launch.classLoader, className.replace('/', '.'), classBytes); } catch (Throwable t) { t.printStackTrace(); } diff --git a/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadFactory.java b/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadFactory.java index 274595d0..8bf84c8f 100644 --- a/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadFactory.java +++ b/src/main/java/zone/rong/loliasm/bakedquad/BakedQuadFactory.java @@ -3,9 +3,10 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.util.EnumFacing; +import zone.rong.loliasm.core.LoliTransformer; /** - * This class aids the dispatches of BakedQuad instances. The create method is removed/patched in {@link zone.rong.loliasm.LoliTransformer} + * This class aids the dispatches of BakedQuad instances. The create method is removed/patched in {@link LoliTransformer} */ public final class BakedQuadFactory { diff --git a/src/main/java/zone/rong/loliasm/bakedquad/VertexDataCache.java b/src/main/java/zone/rong/loliasm/bakedquad/VertexDataCache.java new file mode 100644 index 00000000..857a66f5 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/bakedquad/VertexDataCache.java @@ -0,0 +1,15 @@ +package zone.rong.loliasm.bakedquad; + +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; + +@SuppressWarnings("unused") +public class VertexDataCache { + + private static final ObjectOpenCustomHashSet KNOWN_VERTEX_DATA = new ObjectOpenCustomHashSet<>(4096, IntArrays.HASH_STRATEGY); + + public static int[] canonize(int[] vertexData) { + return KNOWN_VERTEX_DATA.addOrGet(vertexData); + } + +} diff --git a/src/main/java/zone/rong/loliasm/client/models/Deduplicator.java b/src/main/java/zone/rong/loliasm/client/models/Deduplicator.java new file mode 100644 index 00000000..ccc30d47 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/client/models/Deduplicator.java @@ -0,0 +1,343 @@ +package zone.rong.loliasm.client.models; + +import com.google.common.collect.*; +import it.unimi.dsi.fastutil.floats.FloatArrays; +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; +import net.minecraft.client.renderer.block.model.*; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +import net.minecraftforge.client.model.BakedItemModel; +import net.minecraftforge.client.model.PerspectiveMapWrapper; +import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; +import net.minecraftforge.common.model.TRSRTransformation; +import scala.collection.mutable.MultiMap; +import zone.rong.loliasm.LoliLogger; +import zone.rong.loliasm.LoliReflector; +import zone.rong.loliasm.api.HashingStrategies; +import zone.rong.loliasm.api.StringPool; + +import java.lang.invoke.MethodHandle; +import java.util.*; + +/** + * A direct competitor with Foamfix's deduplication solution. + * + * TODO + */ +@SuppressWarnings("rawtypes") +public class Deduplicator { + + private static final MethodHandle UNPACKED_DATA_GETTER = LoliReflector.resolveFieldGetter(UnpackedBakedQuad.class, "unpackedData"); + // private static final MethodHandle UNPACKED_DATA_SETTER = LoliReflector.resolveFieldSetter(UnpackedBakedQuad.class, "unpackedData"); + + private static final MethodHandle SIMPLE_BAKED_MODEL_FACE_QUADS_GETTER = LoliReflector.resolveFieldGetter(SimpleBakedModel.class, "faceQuads", "field_177561_b"); + private static final MethodHandle SIMPLE_BAKED_MODEL_FACE_QUADS_SETTER = LoliReflector.resolveFieldSetter(SimpleBakedModel.class, "faceQuads", "field_177561_b"); + private static final MethodHandle PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_GETTER = LoliReflector.resolveFieldGetter(PerspectiveMapWrapper.class, "transforms"); + private static final MethodHandle PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_SETTER = LoliReflector.resolveFieldSetter(PerspectiveMapWrapper.class, "transforms"); + private static final MethodHandle BAKED_ITEM_MODEL_TRANSFORMS_GETTER = LoliReflector.resolveFieldGetter(BakedItemModel.class, "transforms"); + private static final MethodHandle BAKED_ITEM_MODEL_TRANSFORMS_SETTER = LoliReflector.resolveFieldSetter(BakedItemModel.class, "transforms"); + private static final MethodHandle ITEM_OVERRIDE_LIST_OVERRIDES_GETTER = LoliReflector.resolveFieldGetter(ItemOverrideList.class, "overrides", "field_188023_b"); + private static final MethodHandle ITEM_OVERRIDE_LIST_OVERRIDES_SETTER = LoliReflector.resolveFieldSetter(ItemOverrideList.class, "overrides", "field_188023_b"); + private static final MethodHandle BLOCK_PART_FACE_TEXTURE_GETTER = LoliReflector.resolveFieldGetter(BlockPartFace.class, "texture", "field_178242_d"); + private static final MethodHandle BLOCK_PART_FACE_TEXTURE_SETTER = LoliReflector.resolveFieldSetter(BlockPartFace.class, "texture", "field_178242_d"); + + // Used to check instanceof ImmutableEnumMap which is package-private + private static final ImmutableMap immutableEnumMap; + + static { + EnumMap placeholderEnumMap = new EnumMap<>(Placeholder.class); + placeholderEnumMap.put(Placeholder.ONE, Placeholder.ONE); + placeholderEnumMap.put(Placeholder.TWO, Placeholder.TWO); + placeholderEnumMap.put(Placeholder.THREE, Placeholder.THREE); + immutableEnumMap = Maps.immutableEnumMap(placeholderEnumMap); + } + + private final ObjectOpenCustomHashSet identityCanonicals = new ObjectOpenCustomHashSet<>(HashingStrategies.IDENTITY_OBJECT_HASH); + private final ObjectOpenCustomHashSet genericCanonicals = new ObjectOpenCustomHashSet<>(HashingStrategies.GENERIC_OBJECT_HASH); + /* + @SuppressWarnings("unchecked") + private final Reference2ObjectOpenHashMap javaOptionalCache = new Reference2ObjectOpenHashMap(); + @SuppressWarnings("unchecked") + private final Reference2ObjectOpenHashMap guavaOptionalCache = new Reference2ObjectOpenHashMap(); + */ + private final ObjectOpenCustomHashSet floatArrayCache = new ObjectOpenCustomHashSet<>(FloatArrays.HASH_STRATEGY); + private final ObjectOpenCustomHashSet float2dArrayCache = new ObjectOpenCustomHashSet<>(HashingStrategies.FLOAT_2D_ARRAY_HASH); + private final ObjectOpenCustomHashSet genericObjectCache = new ObjectOpenCustomHashSet<>(HashingStrategies.IDENTITY_OBJECT_HASH); + private final ObjectOpenCustomHashSet itemCameraTransformsCache = new ObjectOpenCustomHashSet<>(HashingStrategies.ITEM_CAMERA_TRANSFORMS_HASH); + + @SuppressWarnings("unchecked") + public final Object deduplicate(Object o) { + if (o == null) { + return null; + } + Object n = genericCanonicals.addOrGet(o); + if (n != o) { + return o; + } + if (o instanceof IBakedModel) { + if (o instanceof SimpleBakedModel) { + /* TODO: SimpleBakedModel$Builder can transform BakedQuads => BakedQuadRetextureds if the ctor with IBakedModel is used. + Add a small check in the builder method to check if the 2 sprites are identical before transforming. + This isn't a big deal atm since LoliASM optimizes BakedQuadRetextureds as well. + */ + trim(((IBakedModel) o).getQuads(null, null, 0L)); // Trim generalQuads + for (EnumFacing facing : EnumFacing.VALUES) { + trim(((IBakedModel) o).getQuads(null, facing, 0L)); // Trim faceQuads + } + try { + Map> faceQuads = (Map>) SIMPLE_BAKED_MODEL_FACE_QUADS_GETTER.invokeExact((SimpleBakedModel) o); + if (!(faceQuads instanceof EnumMap)) { + LoliLogger.instance.debug("Found a non-EnumMap faceQuads instance! Preparing to transform"); + // If faceQuads isn't an EnumMap, we replace it with an EnumMap + SIMPLE_BAKED_MODEL_FACE_QUADS_SETTER.invokeExact((SimpleBakedModel) o, new EnumMap<>(faceQuads)); + } + } catch (Throwable t) { + LoliLogger.instance.throwing(t); + } + return o; + } else if (o instanceof PerspectiveMapWrapper) { + try { + Object transforms = PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_GETTER.invoke(o); + if (transforms != null) { + Object newTransforms = canonize(transforms); + if (transforms != newTransforms) { + LoliLogger.instance.debug("Canonized PerspectiveMapWrapper#transforms successfully."); + PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_SETTER.invoke((PerspectiveMapWrapper) o, newTransforms); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + return o; + } + + /* + public final Object deduplicate(Object o) { + if (o == null || !canonicals.add(o)) { + return o; + } + if (o instanceof IBakedModel) { + if (o instanceof SimpleBakedModel) { + trim(((IBakedModel) o).getQuads(null, null, 0L)); // Trim generalQuads + for (EnumFacing facing : EnumFacing.VALUES) { + trim(((IBakedModel) o).getQuads(null, facing, 0L)); // Trim faceQuads + } + } else if (o instanceof PerspectiveMapWrapper) { + try { + Object transforms = PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_GETTER.invokeExact((PerspectiveMapWrapper) o); + if (transforms != null) { + Object newTransforms = canonize(transforms); + if (transforms != newTransforms) { + PERSPECTIVE_MAP_WRAPPER_TRANSFORMS_SETTER.invokeExact((PerspectiveMapWrapper) o, newTransforms); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } else if (o instanceof BakedItemModel) { + try { + Object transforms = BAKED_ITEM_MODEL_TRANSFORMS_GETTER.invokeExact((BakedItemModel) o); + if (transforms != null) { + Object newTransforms = canonize(transforms); + if (transforms != newTransforms) { + BAKED_ITEM_MODEL_TRANSFORMS_SETTER.invokeExact((BakedItemModel) o, newTransforms); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + } else if (o instanceof BlockPartFace) { // Canonize String => StringPool + BlockPartFace bpf = (BlockPartFace) o; + bpf.blockFaceUV.uvs = (float[]) canonize(bpf.blockFaceUV.uvs); + try { + Object texture = BLOCK_PART_FACE_TEXTURE_GETTER.invokeExact(bpf); + if (texture != null) { + Object newTexture = canonize(texture); + if (texture != newTexture) { + BLOCK_PART_FACE_TEXTURE_SETTER.invokeExact(bpf, newTexture); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + return o; + } else if (o instanceof UnpackedBakedQuad) { + try { + canonize(UNPACKED_DATA_GETTER.invokeExact((UnpackedBakedQuad) o)); + } catch (Throwable t) { + t.printStackTrace(); + } + return o; + } else if (o instanceof ResourceLocation || o instanceof TRSRTransformation || o instanceof Vec3d || o instanceof Vec3i || o instanceof ItemCameraTransforms) { + return canonize(o); + } else if (o instanceof ItemOverrideList && o != ItemOverrideList.NONE) { + try { + List list = (List) ITEM_OVERRIDE_LIST_OVERRIDES_GETTER.invokeExact((ItemOverrideList) o); + if (list.isEmpty()) { + if (o instanceof AnimationItemOverrideList) { + ITEM_OVERRIDE_LIST_OVERRIDES_SETTER.invokeExact((ItemOverrideList) o, (List) ImmutableList.of()); + } else { + return ItemOverrideList.NONE; + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + return o; + } else if (o instanceof Multimap) { + if (o instanceof ImmutableMultimap || o instanceof SortedSetMultimap) { + for (Object value : ((Multimap) o).values()) { + deduplicate(value); + } + } else { + for (Object key : ((Multimap) o).keySet()) { + List l = new ArrayList(((Multimap) o).values()); + for (int i = 0; i < l.size(); i++) { + l.set(i, deduplicate(l.get(i))); + } + ((Multimap) o).replaceValues(key, l); + } + } + return o; + } else if (o instanceof Map) { + for (Object key : ((Map) o).keySet()) { + deduplicate(key); + } + if (o instanceof SortedMap) { + for (Object values : ((Map) o).values()) { + deduplicate(values); + } + } else if (o instanceof ImmutableMap) { + ImmutableMap im = (ImmutableMap) o; + ImmutableMap.Builder newMap = (o instanceof ImmutableBiMap) ? ImmutableBiMap.builder() : ImmutableMap.builder(); + boolean deduplicated = false; + for (Object key : im.keySet()) { + Object a = im.get(key); + Object b = deduplicate(a); + newMap.put(key, b != null ? b : a); + if (b != null && b != a) { + deduplicated = true; + } + } + return deduplicated ? newMap.build() : o; + } else { + try { + for (Object key : ((Map) o).keySet()) { + Object value = ((Map) o).get(key); + Object valueD = deduplicate(value); + if (valueD != null && value != valueD) + ((Map) o).put(key, valueD); + } + } catch (UnsupportedOperationException e) { + e.printStackTrace(); + } + } + return o; + } else if (o instanceof Collection) { + if (o instanceof List) { + if (o instanceof ImmutableList) { + ImmutableList il = (ImmutableList) o; + ImmutableList.Builder builder = ImmutableList.builder(); + boolean deduplicated = false; + for (int i = 0; i < il.size(); i++) { + Object a = il.get(i); + Object b = deduplicate(a); + builder.add(b != null ? b : a); + if (b != null && b != a) + deduplicated = true; + } + if (deduplicated) { + return builder.build(); + } + } else { + List l = (List) o; + try { + for (int i = 0; i < l.size(); i++) { + l.set(i, deduplicate(l.get(i))); + } + } catch (UnsupportedOperationException e) { + e.printStackTrace(); + } + } + return o; + } else if (o instanceof ImmutableSet) { + if (!(o instanceof ImmutableSortedSet)) { + ImmutableSet.Builder builder = new ImmutableSet.Builder(); + for (Object o1 : ((Set) o)) { + builder.add(deduplicate(o1)); + } + o = builder.build(); + } else { + for (Object o1 : ((Set) o)) { + deduplicate(o1); + } + } + return o; + } else if (o instanceof Set && !(o instanceof SortedSet)) { + // Stub + } else { + for (Object o1 : ((Collection) o)) { + deduplicate(o1); + } + return o; + } + } + return o; + } + */ + + private Object canonize(Object o) { + Object n = o; + if (o instanceof float[]) { + n = floatArrayCache.addOrGet((float[]) o); + } else if (o instanceof float[][]) { + float[][] o2 = float2dArrayCache.addOrGet((float[][]) o); + if (o == o2) { + for (int i = 0; i < o2.length; i++) { + o2[i] = (float[]) canonize(o2[i]); + } + } else { + n = o2; + } + } else if (o instanceof float[][][]) { + float[][][] o2 = (float[][][]) o; + for (int i = 0; i < o2.length; i++) { + o2[i] = (float[][]) canonize(o2[i]); + } + } else if (o instanceof Map) { + if (o instanceof ImmutableMap && ((Map) o).isEmpty() && n != ImmutableMap.of()) { + n = ImmutableMap.of(); + } else if (o.getClass() != immutableEnumMap.getClass() && !((Map) o).isEmpty() && ((Map) o).keySet().toArray()[0] instanceof Enum) { + n = new EnumMap<>((Map) o); + } + } else if (o instanceof Collection || o instanceof MultiMap || o instanceof ResourceLocation || o instanceof Vec3d || o instanceof Vec3i || o instanceof TRSRTransformation) { + n = genericObjectCache.addOrGet(o); + } else if (o instanceof ItemCameraTransforms) { + n = itemCameraTransformsCache.addOrGet((ItemCameraTransforms) o); + } else if (o instanceof String) { + n = StringPool.canonize((String) o); + } + if (n != o) { + LoliLogger.instance.info("Deduplicated {}@{} => {}@{} successfully", o.getClass().getName(), Integer.toHexString(o.hashCode()), n.getClass().getName(), Integer.toHexString(n.hashCode())); + } + return n; + } + + private void trim(Object o) { + if (o instanceof ArrayList) { + ((ArrayList) o).trimToSize(); + } + } + + private enum Placeholder { + ONE, + TWO, + THREE; + } + +} diff --git a/src/main/java/zone/rong/loliasm/client/models/MultipartBakedModelCache.java b/src/main/java/zone/rong/loliasm/client/models/MultipartBakedModelCache.java new file mode 100644 index 00000000..24f80e35 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/client/models/MultipartBakedModelCache.java @@ -0,0 +1,36 @@ +package zone.rong.loliasm.client.models; + +import com.google.common.base.Predicate; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.MultipartBakedModel; + +import java.util.Map; + +/** + * TODO: Not used at the moment, will be activated with the Deduplicator when that is complete + */ +public class MultipartBakedModelCache { + + public static int total = 0; + public static int unique = 0; + + private static final Map, IBakedModel>, MultipartBakedModel> KNOWN_MULTIPART_MODELS = new Object2ObjectOpenHashMap<>(32); + + public static MultipartBakedModel makeMultipartModel(Map, IBakedModel> selectors) { + MultipartBakedModel multipartBakedModel = KNOWN_MULTIPART_MODELS.get(selectors); + total++; + if (multipartBakedModel == null) { + unique++; + KNOWN_MULTIPART_MODELS.put(selectors, multipartBakedModel = new MultipartBakedModel(selectors)); + } + return multipartBakedModel; + // return KNOWN_MULTIPART_MODELS.computeIfAbsent(selectors, MultipartBakedModel::new); + } + + public static void destroyCache() { + KNOWN_MULTIPART_MODELS.clear(); + } + +} diff --git a/src/main/java/zone/rong/loliasm/client/models/conditions/CanonicalConditions.java b/src/main/java/zone/rong/loliasm/client/models/conditions/CanonicalConditions.java new file mode 100644 index 00000000..1a6ed4f3 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/client/models/conditions/CanonicalConditions.java @@ -0,0 +1,128 @@ +package zone.rong.loliasm.client.models.conditions; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Splitter; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrays; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.block.model.multipart.ICondition; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.*; + +/** + * Huge thanks to the original implementation from malte0811's FerriteCore: https://github.com/malte0811/FerriteCore + * + * Improvements: + * - In 1.12, concurrency isn't needed; hence normal fastutils structures are used rather than ConcurrentMaps + * - Arrays are used rather than Lists + * - Also canonized TRUE/FALSE IConditions' inner predicates + */ +@SuppressWarnings({"unused", "Guava"}) +public class CanonicalConditions { + + public static final ICondition TRUE = state -> Predicates.alwaysTrue(); + public static final ICondition FALSE = state -> Predicates.alwaysFalse(); + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final Map[], Predicate> OR_CACHE = new Object2ObjectOpenCustomHashMap<>(32, ObjectArrays.HASH_STRATEGY); + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final Map[], Predicate> AND_CACHE = new Object2ObjectOpenCustomHashMap<>(32, ObjectArrays.HASH_STRATEGY); + + private static final Map, Comparable>, Predicate> STATE_HAS_PROPERTY_CACHE = new Object2ObjectOpenHashMap<>(32); + + public static void destroyCache() { + OR_CACHE.clear(); + AND_CACHE.clear(); + STATE_HAS_PROPERTY_CACHE.clear(); + } + + public static Predicate orCache(Iterable conditions, BlockStateContainer stateContainer) { + return OR_CACHE.computeIfAbsent(canonize(conditions, stateContainer), CanonicalConditions::orChain); + } + + public static Predicate orCache(Predicate[] array) { + return OR_CACHE.computeIfAbsent(array, CanonicalConditions::orChain); + } + + public static Predicate andCache(Iterable conditions, BlockStateContainer stateContainer) { + return AND_CACHE.computeIfAbsent(canonize(conditions, stateContainer), CanonicalConditions::andChain); + } + + @SuppressWarnings({"unchecked", "UnstableApiUsage"}) + public static Predicate propertyValueCache(BlockStateContainer stateContainer, String key, String value, Splitter splitter) { + IProperty property = stateContainer.getProperty(key); + if (property == null) { + throw new RuntimeException(String.format("Unknown property '%s' on '%s'", key, stateContainer.getBlock().toString())); + } else { + String valueNoInvert = value; + boolean invert = !valueNoInvert.isEmpty() && valueNoInvert.charAt(0) == '!'; + if (invert) { + valueNoInvert = valueNoInvert.substring(1); + } + List matchedStates = splitter.splitToList(valueNoInvert); + if (matchedStates.isEmpty()) { + throw new RuntimeException(String.format("Empty value '%s' for property '%s' on '%s'", value, key, stateContainer.getBlock().toString())); + } else { + Predicate isMatchedState; + if (matchedStates.size() == 1) { + isMatchedState = makePropertyPredicate(stateContainer, property, valueNoInvert, key, value); + } else { + isMatchedState = orCache(matchedStates.stream() + .map(subValue -> makePropertyPredicate(stateContainer, property, subValue, key, value)) + .sorted(Comparator.comparingInt(Predicate::hashCode)) + .toArray(Predicate[]::new)); + } + return invert ? Predicates.not(isMatchedState) : isMatchedState; + } + } + } + + private static Predicate orChain(Predicate[] array) { + return state -> { + for (Predicate predicate : array) { + if (predicate.test(state)) { + return true; + } + } + return false; + }; + } + + private static Predicate andChain(Predicate[] array) { + return state -> { + for (Predicate predicate : array) { + if (!predicate.test(state)) { + return false; + } + } + return true; + }; + } + + @SuppressWarnings("unchecked") + private static Predicate[] canonize(Iterable conditions, BlockStateContainer stateContainer) { + ArrayList> list = new ArrayList<>(); + for (ICondition cond : conditions) { + list.add(cond.getPredicate(stateContainer)); + } + Predicate[] array = list.toArray(new Predicate[0]); + Arrays.sort(array, Comparator.comparingInt(Predicate::hashCode)); + return array; + } + + @SuppressWarnings("ConstantConditions") + private static > Predicate makePropertyPredicate(BlockStateContainer container, IProperty property, String subValue, String key, String value) { + Optional optional = property.parseValue(subValue); + if (!optional.isPresent()) { + throw new RuntimeException(String.format("Unknown value '%s' for property '%s' on '%s' in '%s'", subValue, key, container.getBlock().toString(), value)); + } else { + return STATE_HAS_PROPERTY_CACHE.computeIfAbsent(Pair.of(property, optional.get()), p -> s -> s.getValue(p.getLeft()).equals(p.getRight())); + } + } +} diff --git a/src/main/java/zone/rong/loliasm/common/memory/mixins/LockCodeMixin.java b/src/main/java/zone/rong/loliasm/common/memory/mixins/LockCodeMixin.java new file mode 100644 index 00000000..7f3e3e42 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/common/memory/mixins/LockCodeMixin.java @@ -0,0 +1,32 @@ +package zone.rong.loliasm.common.memory.mixins; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.LockCode; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +/** + * Fixes LockCode not using its EMPTY variant when loading the EMPTY one from nbt + */ +@Mixin(LockCode.class) +public class LockCodeMixin { + + @Shadow @Final public static LockCode EMPTY_CODE; + + /** + * @author Rongmario + * @reason Use EMPTY_CODE if Lock string isEmpty, which happens every time a tile that uses this is loaded from nbt + */ + @Overwrite + public static LockCode fromNBT(NBTTagCompound nbt) { + if (nbt.hasKey("Lock", 8)) { + String s = nbt.getString("Lock"); + return s.isEmpty() ? EMPTY_CODE : new LockCode(s); + } else { + return EMPTY_CODE; + } + } + +} diff --git a/src/main/java/zone/rong/loliasm/common/recipes/mixins/FurnaceRecipesMixin.java b/src/main/java/zone/rong/loliasm/common/recipes/mixins/FurnaceRecipesMixin.java new file mode 100644 index 00000000..01451e28 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/common/recipes/mixins/FurnaceRecipesMixin.java @@ -0,0 +1,62 @@ +package zone.rong.loliasm.common.recipes.mixins; + +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraftforge.fml.common.FMLLog; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; + +/** + * FurnaceRecipes are only item/meta sensitive, hence a simple Strategy is used. + * + * getSmeltingResult is now only a hash lookup. + * experienceList doesn't have its floats boxed now. + */ +@Mixin(FurnaceRecipes.class) +public abstract class FurnaceRecipesMixin { + + @Shadow @Final private Map smeltingList; + @Shadow @Final private Map experienceList; + + /** + * @author Rongmario + * @reason Use Object2FloatMap#put + */ + @Overwrite + public void addSmeltingRecipe(ItemStack input, ItemStack stack, float experience) { + if (getSmeltingResult(input) != ItemStack.EMPTY) { + FMLLog.log.info("Ignored smelting recipe with conflicting input: {} = {}", input, stack); return; + } + this.smeltingList.put(input, stack); + ((Object2FloatMap) this.experienceList).put(stack, experience); + } + + /** + * @author Rongmario + * @reason Efficient getSmeltingResult + */ + @Overwrite + public ItemStack getSmeltingResult(ItemStack stack) { + ItemStack result = this.smeltingList.get(stack); + return result == null ? ItemStack.EMPTY : result; + } + + /** + * @author Rongmario + * @reason Efficient getSmeltingExperience + */ + @Overwrite + public float getSmeltingExperience(ItemStack stack) { + float exp = stack.getItem().getSmeltingExperience(stack); + if (exp == -1) { + exp = ((Object2FloatMap) this.experienceList).getFloat(stack); + } + return exp; + } + +} diff --git a/src/main/java/zone/rong/loliasm/common/registries/mixins/RegistrySimpleMixin.java b/src/main/java/zone/rong/loliasm/common/registries/mixins/RegistrySimpleMixin.java new file mode 100644 index 00000000..ae64790c --- /dev/null +++ b/src/main/java/zone/rong/loliasm/common/registries/mixins/RegistrySimpleMixin.java @@ -0,0 +1,69 @@ +package zone.rong.loliasm.common.registries.mixins; + +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import net.minecraft.util.registry.RegistrySimple; +import org.apache.commons.lang3.Validate; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import zone.rong.loliasm.LoliASM; +import zone.rong.loliasm.api.mixins.RegistrySimpleExtender; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Random; + +@Mixin(RegistrySimple.class) +public class RegistrySimpleMixin implements RegistrySimpleExtender { + + @Shadow @Final private static Logger LOGGER; + + @Shadow @Final protected Map registryObjects; + + /** + * @author Rongmario + * @reason Better map structure + */ + @Overwrite + protected Map createUnderlyingMap() { + LoliASM.simpleRegistryInstances.add(this); + return new Object2ReferenceOpenHashMap<>(64); + } + + /** + * @author Rongmario + * @reason Remove values reference + */ + @Overwrite + public void putObject(K key, V value) { + Validate.notNull(key); + Validate.notNull(value); + if (this.registryObjects.containsKey(key)) { + LOGGER.debug("Adding duplicate key '{}' to registry", key); + } + this.registryObjects.put(key, value); + } + + /** + * @author Rongmario + * @reason Remove values reference + use streams to gather a random object + */ + @Nullable + @Overwrite + public V getRandomObject(Random random) { + return this.registryObjects.values().stream().skip(random.nextInt(this.registryObjects.size())).findFirst().get(); + } + + @Override + public void clearUnderlyingMap() { + this.registryObjects.clear(); + } + + @Override + public void trim() { + ((Object2ReferenceOpenHashMap) this.registryObjects).trim(); + } + +} diff --git a/src/main/java/zone/rong/loliasm/common/registries/mixins/SoundRegistryMixin.java b/src/main/java/zone/rong/loliasm/common/registries/mixins/SoundRegistryMixin.java new file mode 100644 index 00000000..f76bb653 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/common/registries/mixins/SoundRegistryMixin.java @@ -0,0 +1,34 @@ +package zone.rong.loliasm.common.registries.mixins; + +import net.minecraft.client.audio.SoundEventAccessor; +import net.minecraft.client.audio.SoundRegistry; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.registry.RegistrySimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import zone.rong.loliasm.api.mixins.RegistrySimpleExtender; + +import java.util.Map; + +@Mixin(SoundRegistry.class) +public abstract class SoundRegistryMixin extends RegistrySimple implements RegistrySimpleExtender { + + /** + * @author Rongmario + * @reason Use RegistrySimple#createUnderlyingMap + */ + @Overwrite + protected Map createUnderlyingMap() { + return super.createUnderlyingMap(); + } + + /** + * @author Rongmario + * @reason Use RegistrySimpleExtender#clearUnderlyingMap + */ + @Overwrite + public void clearMap() { + this.clearUnderlyingMap(); + } + +} diff --git a/src/main/java/zone/rong/loliasm/core/LoliFMLCallHook.java b/src/main/java/zone/rong/loliasm/core/LoliFMLCallHook.java new file mode 100644 index 00000000..a5fbf02b --- /dev/null +++ b/src/main/java/zone/rong/loliasm/core/LoliFMLCallHook.java @@ -0,0 +1,49 @@ +package zone.rong.loliasm.core; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import net.minecraftforge.fml.relauncher.IFMLCallHook; +import zone.rong.loliasm.LoliConfig; +import zone.rong.loliasm.LoliReflector; +import zone.rong.loliasm.api.datastructures.canonical.AutoCanonizingSet; +import zone.rong.loliasm.api.datastructures.deobf.DeobfuscatedMappingsMap; +import zone.rong.loliasm.api.datastructures.deobf.FieldDescriptionsMap; +import zone.rong.loliasm.api.StringPool; + +import java.util.Map; +import java.util.Set; + +public class LoliFMLCallHook implements IFMLCallHook { + + @Override + @SuppressWarnings("unchecked") + public Void call() { + try { + // This deduplicates 50% of data in deobfuscated environments + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "classNameBiMap").invokeExact(FMLDeobfuscatingRemapper.INSTANCE, canonizeClassNames((BiMap) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "classNameBiMap").invokeExact(FMLDeobfuscatingRemapper.INSTANCE))); + if (!FMLLaunchHandler.isDeobfuscatedEnvironment() && LoliConfig.getConfig().remapperMemorySaver) { + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "rawFieldMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE, DeobfuscatedMappingsMap.of((Map>) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "rawFieldMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE), true)); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "rawMethodMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE, DeobfuscatedMappingsMap.of((Map>) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "rawMethodMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE), false)); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "fieldNameMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE, DeobfuscatedMappingsMap.of((Map>) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "fieldNameMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE), true)); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "methodNameMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE, DeobfuscatedMappingsMap.of((Map>) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "methodNameMaps").invokeExact(FMLDeobfuscatingRemapper.INSTANCE), false)); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "fieldDescriptions").invoke(FMLDeobfuscatingRemapper.INSTANCE, new FieldDescriptionsMap((Map>) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "fieldDescriptions").invokeExact(FMLDeobfuscatingRemapper.INSTANCE))); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "negativeCacheMethods").invoke(FMLDeobfuscatingRemapper.INSTANCE, new AutoCanonizingSet<>((Set) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "negativeCacheMethods").invokeExact(FMLDeobfuscatingRemapper.INSTANCE))); + LoliReflector.resolveFieldSetter(FMLDeobfuscatingRemapper.class, "negativeCacheFields").invoke(FMLDeobfuscatingRemapper.INSTANCE, new AutoCanonizingSet<>((Set) LoliReflector.resolveFieldGetter(FMLDeobfuscatingRemapper.class, "negativeCacheFields").invokeExact(FMLDeobfuscatingRemapper.INSTANCE))); + } + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + @Override + public void injectData(Map data) { } + + private BiMap canonizeClassNames(BiMap map) { + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + map.forEach((s1, s2) -> builder.put(StringPool.canonize(s1), StringPool.canonize(s2))); + return builder.build(); + } +} diff --git a/src/main/java/zone/rong/loliasm/core/LoliLoadingPlugin.java b/src/main/java/zone/rong/loliasm/core/LoliLoadingPlugin.java new file mode 100644 index 00000000..0536d5d2 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/core/LoliLoadingPlugin.java @@ -0,0 +1,75 @@ +package zone.rong.loliasm.core; + +import com.google.common.cache.CacheBuilder; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.Mixins; +import zone.rong.loliasm.LoliConfig; +import zone.rong.loliasm.LoliLogger; +import zone.rong.loliasm.LoliReflector; +import zone.rong.loliasm.api.datastructures.DummyMap; + +import java.util.Map; + +@IFMLLoadingPlugin.Name("LoliASM") +@IFMLLoadingPlugin.MCVersion(ForgeVersion.mcVersion) +public class LoliLoadingPlugin implements IFMLLoadingPlugin { + + public LoliLoadingPlugin() { + LoliLogger.instance.info("Lolis are loading in some mixins since Rongmario's too lazy to write pure ASM all the time despite the mod being called 'LoliASM'"); + MixinBootstrap.init(); + LoliConfig.Data data = LoliConfig.getConfig(); + if (data.optimizeDataStructures) { + Mixins.addConfiguration("mixins.registries.json"); + Mixins.addConfiguration("mixins.memory.json"); + } + if (data.optimizeFurnaceRecipes) { + Mixins.addConfiguration("mixins.recipes.json"); + } + if (data.cleanupLaunchClassLoader) { + LoliLogger.instance.info("Replacing LaunchClassLoader fields with dummy maps/sets."); + replaceLaunchClassLoaderFields(); + } + } + + @Override + public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return "zone.rong.loliasm.core.LoliFMLCallHook"; + // return null; + } + + @Override + public void injectData(Map data) { } + + @Override + public String getAccessTransformerClass() { + // return null; + return "zone.rong.loliasm.core.LoliTransformer"; + } + + void replaceLaunchClassLoaderFields() { + try { + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "cachedClasses").invoke(Launch.classLoader, CacheBuilder.newBuilder().concurrencyLevel(2).weakValues().build().asMap()); + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "invalidClasses").invokeExact(Launch.classLoader, DummyMap.asSet()); + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "packageManifests").invoke(Launch.classLoader, DummyMap.of()); + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "resourceCache").invoke(Launch.classLoader, DummyMap.of()); + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "negativeResourceCache").invokeExact(Launch.classLoader, DummyMap.asSet()); + LoliReflector.resolveFieldSetter(LaunchClassLoader.class, "EMPTY").invoke(null); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/zone/rong/loliasm/core/LoliTransformer.java b/src/main/java/zone/rong/loliasm/core/LoliTransformer.java new file mode 100644 index 00000000..21580df4 --- /dev/null +++ b/src/main/java/zone/rong/loliasm/core/LoliTransformer.java @@ -0,0 +1,427 @@ +package zone.rong.loliasm.core; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import org.objectweb.asm.*; +import org.objectweb.asm.tree.*; +import zone.rong.loliasm.LoliConfig; +import zone.rong.loliasm.LoliLogger; +import zone.rong.loliasm.patches.*; + +import java.util.ListIterator; +import java.util.Map; +import java.util.function.Function; + +import static org.objectweb.asm.Opcodes.*; + +public class LoliTransformer implements IClassTransformer { + + public static final boolean isDeobf = FMLLaunchHandler.isDeobfuscatedEnvironment(); + + final Map> transformations; + + public LoliTransformer() { + LoliLogger.instance.info("The lolis are now preparing to bytecode manipulate your game."); + LoliConfig.Data data = LoliConfig.getConfig(); + transformations = new Object2ObjectOpenHashMap<>(5 + data.bakedQuadPatchClasses.length); + addTransformation("net.minecraft.client.renderer.block.model.BakedQuad", BakedQuadPatch::rewriteBakedQuad); + addTransformation("net.minecraft.client.renderer.block.model.BakedQuadRetextured", BakedQuadRetexturedPatch::patchBakedQuadRetextured); + addTransformation("net.minecraftforge.client.model.pipeline.UnpackedBakedQuad", UnpackedBakedQuadPatch::rewriteUnpackedBakedQuad); + addTransformation("net.minecraftforge.client.model.pipeline.UnpackedBakedQuad$Builder", UnpackedBakedQuadPatch::rewriteUnpackedBakedQuad$Builder); + addTransformation("zone.rong.loliasm.bakedquad.BakedQuadFactory", BakedQuadFactoryPatch::patchCreateMethod); + for (String classToPatch : data.bakedQuadPatchClasses) { + addTransformation(classToPatch, this::redirectNewBakedQuadCalls$EventBased); + } + if (data.canonizeObjects) { + addTransformation("net.minecraft.util.ResourceLocation", this::canonizeResourceLocationStrings); + addTransformation("net.minecraft.client.renderer.block.model.ModelResourceLocation", this::canonizeResourceLocationStrings); + addTransformation("net.minecraft.client.renderer.block.model.multipart.ICondition", this::canonicalBoolConditions); + addTransformation("net.minecraft.client.renderer.block.model.multipart.ConditionOr", bytes -> canonicalPredicatedConditions(bytes, true)); + addTransformation("net.minecraft.client.renderer.block.model.multipart.ConditionAnd", bytes -> canonicalPredicatedConditions(bytes, false)); + addTransformation("net.minecraft.client.renderer.block.model.multipart.ConditionPropertyValue", this::canonicalPropertyValueConditions); + // addTransformation("net.minecraft.client.renderer.block.model.MultipartBakedModel$Builder", this::cacheMultipartBakedModels); TODO + } + if (data.optimizeDataStructures) { + addTransformation("net.minecraft.util.registry.RegistrySimple", this::removeValuesArrayFromRegistrySimple); + addTransformation("net.minecraft.client.audio.SoundRegistry", this::removeDupeMapFromSoundRegistry); + addTransformation("net.minecraft.client.audio.SoundEventAccessor", this::removeInstancedRandom); + addTransformation("net.minecraft.nbt.NBTTagCompound", this::nbtTagCompound$replaceDefaultHashMap); + } + if (data.optimizeFurnaceRecipes) { + addTransformation("net.minecraft.item.crafting.FurnaceRecipes", this::improveFurnaceRecipes); + } + } + + public void addTransformation(String key, Function value) { + LoliLogger.instance.info("Adding class {} to the transformation queue", key); + transformations.put(key, value); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + Function getBytes = transformations.get(transformedName); + if (getBytes != null) { + return getBytes.apply(bytes); + } + return bytes; + } + + // Better way + private byte[] redirectNewBakedQuadCalls$EventBased(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); // in certain cases, this flag is needed + + MethodInsnNode node = new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/bakedquad/BakedQuadFactory", "create", "([IILnet/minecraft/util/EnumFacing;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;ZLnet/minecraft/client/renderer/vertex/VertexFormat;)Lnet/minecraft/client/renderer/block/model/BakedQuad;", false); + reader.accept(new RedirectNewWithStaticCallClassVisitor(writer, "net/minecraft/client/renderer/block/model/BakedQuad", node), 0); + + LoliLogger.instance.info("Redirecting {}'s BakedQuad calls", reader.getClassName()); + + return writer.toByteArray(); + } + + private byte[] canonizeResourceLocationStrings(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + for (MethodNode method : node.methods) { + if (method.name.equals("") && method.desc.equals("(I[Ljava/lang/String;)V")) { + ListIterator iter = method.instructions.iterator(); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction.getOpcode() == GETSTATIC) { + LoliLogger.instance.info("Injecting calls in {} to canonize strings", node.name); + iter.remove(); // Remove GETSTATIC + iter.next(); // Move to INVOKEVIRTUAL, set replaces it + iter.set(new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/api/StringPool", "lowerCaseAndCanonize", "(Ljava/lang/String;)Ljava/lang/String;", false)); + break; + } + } + } + } + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] canonicalBoolConditions(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + for (MethodNode method : node.methods) { + if (method.name.equals("")) { + ListIterator iter = method.instructions.iterator(); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction instanceof TypeInsnNode && instruction.getOpcode() == NEW) { + boolean bool = ((TypeInsnNode) instruction).desc.endsWith("$1"); + LoliLogger.instance.info("Canonizing {} IConditions", bool ? "TRUE" : "FALSE"); + iter.remove(); // Remove NEW + iter.next(); + iter.remove(); // Remove DUP + iter.next(); + iter.remove(); // Remove INVOKESPECIAL + iter.add(new FieldInsnNode(GETSTATIC, "zone/rong/loliasm/client/models/conditions/CanonicalConditions", bool ? "TRUE" : "FALSE", "Lnet/minecraft/client/renderer/block/model/multipart/ICondition;")); + } + } + } + } + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + + return writer.toByteArray(); + } + + private byte[] canonicalPredicatedConditions(byte[] bytes, boolean or) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + final String getPredicate = isDeobf ? "getPredicate" : "func_188118_a"; + + for (MethodNode method : node.methods) { + if (method.name.equals(getPredicate)) { + final String conditions = isDeobf ? "conditions" : or ? "field_188127_c" : "field_188121_c"; + LoliLogger.instance.info("Transforming {}::getPredicate to canonize different IConditions", node.name); + method.instructions.clear(); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new FieldInsnNode(GETFIELD, node.name, conditions, "Ljava/lang/Iterable;")); + method.instructions.add(new VarInsnNode(ALOAD, 1)); + method.instructions.add(new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/client/models/conditions/CanonicalConditions", or ? "orCache" : "andCache", "(Ljava/lang/Iterable;Lnet/minecraft/block/state/BlockStateContainer;)Lcom/google/common/base/Predicate;", false)); + method.instructions.add(new InsnNode(ARETURN)); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + node.accept(writer); + + return writer.toByteArray(); + } + + private byte[] canonicalPropertyValueConditions(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + final String getPredicate = isDeobf ? "getPredicate" : "func_188118_a"; + + for (MethodNode method : node.methods) { + if (method.name.equals(getPredicate)) { + LoliLogger.instance.info("Transforming {}::getPredicate to canonize different PropertyValueConditions", node.name); + method.instructions.clear(); + method.instructions.add(new VarInsnNode(ALOAD, 1)); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new FieldInsnNode(GETFIELD, node.name, isDeobf ? "key" : "field_188125_d", "Ljava/lang/String;")); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new FieldInsnNode(GETFIELD, node.name, isDeobf ? "value" : "field_188126_e", "Ljava/lang/String;")); + method.instructions.add(new FieldInsnNode(GETSTATIC, node.name, isDeobf ? "SPLITTER" : "field_188124_c", "Lcom/google/common/base/Splitter;")); + method.instructions.add(new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/client/models/conditions/CanonicalConditions", "propertyValueCache", "(Lnet/minecraft/block/state/BlockStateContainer;Ljava/lang/String;Ljava/lang/String;Lcom/google/common/base/Splitter;)Lcom/google/common/base/Predicate;", false)); + method.instructions.add(new InsnNode(ARETURN)); + // method.localVariables.remove(0); + method.localVariables.clear(); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + node.accept(writer); + + return writer.toByteArray(); + } + + private byte[] cacheMultipartBakedModels(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + final String makeMultipartModel = isDeobf ? "makeMultipartModel" : "func_188647_a"; + + for (MethodNode method : node.methods) { + if (method.name.equals(makeMultipartModel)) { + LoliLogger.instance.info("Transforming {}::makeMultipartModel", node.name); + final String builderSelectors = isDeobf ? "builderSelectors" : "field_188649_a"; + method.instructions.clear(); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new FieldInsnNode(GETFIELD, node.name, builderSelectors, "Ljava/util/Map;")); + method.instructions.add(new MethodInsnNode(INVOKESTATIC, "zone/rong/loliasm/client/models/MultipartBakedModelCache", "makeMultipartModel", "(Ljava/util/Map;)Lnet/minecraft/client/renderer/block/model/MultipartBakedModel;", false)); + method.instructions.add(new InsnNode(ARETURN)); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] removeValuesArrayFromRegistrySimple(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + final String values = isDeobf ? "values" : "field_186802_b"; + + node.fields.removeIf(f -> f.name.equals(values)); + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] removeDupeMapFromSoundRegistry(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + final String soundRegistry = isDeobf ? "soundRegistry" : "field_148764_a"; + + node.fields.removeIf(f -> f.name.equals(soundRegistry)); + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] improveFurnaceRecipes(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + for (MethodNode method : node.methods) { + if (method.name.equals("")) { + ListIterator iter = method.instructions.iterator(); + boolean isExperienceList = false; + LoliLogger.instance.info("Improving FurnaceRecipes. Lookups are now a lot faster."); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction instanceof MethodInsnNode) { + MethodInsnNode methodInstruction = (MethodInsnNode) instruction; + if (methodInstruction.owner.equals("com/google/common/collect/Maps")) { + iter.remove(); + if (!isExperienceList) { + iter.add(new TypeInsnNode(NEW, "it/unimi/dsi/fastutil/objects/Object2ObjectOpenCustomHashMap")); + iter.add(new InsnNode(DUP)); + iter.add(new FieldInsnNode(GETSTATIC, "zone/rong/loliasm/api/HashingStrategies", "ITEM_AND_META_HASH", "Lit/unimi/dsi/fastutil/Hash$Strategy;")); + iter.add(new MethodInsnNode(INVOKESPECIAL, "it/unimi/dsi/fastutil/objects/Object2ObjectOpenCustomHashMap", "", "(Lit/unimi/dsi/fastutil/Hash$Strategy;)V", false)); + isExperienceList = true; + } else { + iter.add(new TypeInsnNode(NEW, "it/unimi/dsi/fastutil/objects/Object2FloatArrayMap")); + iter.add(new InsnNode(DUP)); + iter.add(new MethodInsnNode(INVOKESPECIAL, "it/unimi/dsi/fastutil/objects/Object2FloatArrayMap", "", "()V", false)); + iter.next(); + iter.add(new VarInsnNode(ALOAD, 0)); + iter.add(new FieldInsnNode(GETFIELD, "net/minecraft/item/crafting/FurnaceRecipes", isDeobf ? "experienceList" : "field_77605_c", "Ljava/util/Map;")); + iter.add(new TypeInsnNode(CHECKCAST, "it/unimi/dsi/fastutil/objects/Object2FloatFunction")); + iter.add(new LdcInsnNode(1F)); + iter.add(new MethodInsnNode(INVOKEINTERFACE, "it/unimi/dsi/fastutil/objects/Object2FloatFunction", "defaultReturnValue", "(F)V", true)); + } + } + } + } + } + } + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] removeInstancedRandom(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + node.fields.removeIf(f -> f.desc.equals("Ljava/util/Random;")); + + for (MethodNode method : node.methods) { + if (method.name.equals("")) { + ListIterator iter = method.instructions.iterator(); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction instanceof TypeInsnNode) { + TypeInsnNode newNode = (TypeInsnNode) instruction; + if (newNode.desc.equals("java/util/Random")) { + iter.previous(); + iter.remove(); // Remove ALOAD + iter.next(); + iter.remove(); // Remove NEW + iter.next(); + iter.remove(); // Remove DUP + iter.next(); + iter.remove(); // Remove INVOKESPECIAL + iter.next(); + iter.remove(); // Remove PUTFIELD + } + } + } + } else if (method.name.equals(isDeobf ? "cloneEntry" : "func_148720_g")) { + ListIterator iter = method.instructions.iterator(); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction.getOpcode() == GETFIELD) { + if (((FieldInsnNode) instruction).desc.equals("Ljava/util/Random;")) { + iter.previous(); + iter.remove(); // Remove ALOAD + iter.next(); + iter.remove(); // Remove GETFIELD + iter.next(); + iter.remove(); // Remove ILOAD + iter.add(new MethodInsnNode(INVOKESTATIC, "java/util/concurrent/ThreadLocalRandom", "current", "()Ljava/util/concurrent/ThreadLocalRandom;", false)); + iter.add(new IntInsnNode(ILOAD, 1)); + iter.add(new MethodInsnNode(INVOKEVIRTUAL, "java/util/concurrent/ThreadLocalRandom", "nextInt", "(I)I", false)); + } + } + } + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + node.accept(writer); + return writer.toByteArray(); + } + + private byte[] nbtTagCompound$replaceDefaultHashMap(byte[] bytes) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + for (MethodNode method : node.methods) { + if (method.name.equals("")) { + ListIterator iter = method.instructions.iterator(); + while (iter.hasNext()) { + AbstractInsnNode instruction = iter.next(); + if (instruction.getOpcode() == INVOKESTATIC) { + iter.set(new TypeInsnNode(NEW, "it/unimi/dsi/fastutil/objects/Object2ObjectArrayMap")); + iter.add(new InsnNode(DUP)); + iter.add(new MethodInsnNode(INVOKESPECIAL, "it/unimi/dsi/fastutil/objects/Object2ObjectArrayMap", "", "()V", false)); + break; + } + } + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + node.accept(writer); + return writer.toByteArray(); + } + + static class RedirectNewWithStaticCallClassVisitor extends ClassVisitor { + + final String ownerOfNewCall; + final MethodInsnNode staticRedirect; + + RedirectNewWithStaticCallClassVisitor(ClassVisitor cv, String ownerOfNewCall, MethodInsnNode staticRedirect) { + super(ASM5, cv); + this.ownerOfNewCall = ownerOfNewCall; + this.staticRedirect = staticRedirect; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new ReplaceNewWithStaticCall(ASM5, cv.visitMethod(access, name, desc, signature, exceptions), ownerOfNewCall, staticRedirect); + } + } + + static class ReplaceNewWithStaticCall extends MethodVisitor { + + final String ownerOfNewCall; + final MethodInsnNode staticRedirect; + + boolean foundNewCall = false; + + ReplaceNewWithStaticCall(int api, MethodVisitor mv, String ownerOfNewCall, MethodInsnNode staticRedirect) { + super(api, mv); + this.ownerOfNewCall = ownerOfNewCall; + this.staticRedirect = staticRedirect; + } + + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == NEW && type.equals(ownerOfNewCall)) { + foundNewCall = true; + } else { + super.visitTypeInsn(opcode, type); + } + } + + @Override + public void visitInsn(int opcode) { + if (foundNewCall && opcode == DUP) { + foundNewCall = false; + } else { + super.visitInsn(opcode); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKESPECIAL && owner.equals(ownerOfNewCall) && name.equals("")) { + super.visitMethodInsn(staticRedirect.getOpcode(), staticRedirect.owner, staticRedirect.name, staticRedirect.desc, staticRedirect.itf); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } +} diff --git a/src/main/java/zone/rong/loliasm/patches/BakedQuadFactoryPatch.java b/src/main/java/zone/rong/loliasm/patches/BakedQuadFactoryPatch.java index b76a0d6a..e2e6177f 100644 --- a/src/main/java/zone/rong/loliasm/patches/BakedQuadFactoryPatch.java +++ b/src/main/java/zone/rong/loliasm/patches/BakedQuadFactoryPatch.java @@ -10,6 +10,7 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import zone.rong.loliasm.bakedquad.BakedQuadClassFactory; +import zone.rong.loliasm.core.LoliTransformer; import java.util.ListIterator; @@ -17,7 +18,7 @@ /** * This class contains class writers for patched BakedQuadFactory - * defineClass not called here, pass raw byte[] back to {@link zone.rong.loliasm.LoliTransformer#transform(String, String, byte[])} + * defineClass not called here, pass raw byte[] back to {@link LoliTransformer#transform(String, String, byte[])} * * No optimizations for this patch, it is also not a whole class patch. * Just before the transformation, we predefine the classes via ASM in {@link BakedQuadClassFactory} diff --git a/src/main/java/zone/rong/loliasm/patches/BakedQuadPatch.java b/src/main/java/zone/rong/loliasm/patches/BakedQuadPatch.java index 1b800d96..02c938dd 100644 --- a/src/main/java/zone/rong/loliasm/patches/BakedQuadPatch.java +++ b/src/main/java/zone/rong/loliasm/patches/BakedQuadPatch.java @@ -2,7 +2,7 @@ import org.objectweb.asm.*; import zone.rong.loliasm.LoliConfig; -import zone.rong.loliasm.LoliTransformer; +import zone.rong.loliasm.core.LoliTransformer; import static org.objectweb.asm.Opcodes.*; @@ -10,7 +10,7 @@ * TODO Commenting * * This class contains class writers for patched BakedQuad - * defineClass not called here, pass raw byte[] back to {@link zone.rong.loliasm.LoliTransformer#transform(String, String, byte[])} + * defineClass not called here, pass raw byte[] back to {@link LoliTransformer#transform(String, String, byte[])} * * Optimizations: * @@ -128,7 +128,7 @@ public static byte[] rewriteBakedQuad(byte[] originalClass) { methodVisitor.visitLabel(l4); methodVisitor.visitLineNumber(44, l4); Label startingLabel = null; - if (LoliConfig.config.logClassesThatNeedPatching) { + if (LoliConfig.getConfig().logClassesThatNeedPatching) { methodVisitor.visitFieldInsn(GETSTATIC, "me/nallar/whocalled/WhoCalled", "$", "Lme/nallar/whocalled/WhoCalled;"); methodVisitor.visitInsn(ICONST_1); methodVisitor.visitMethodInsn(INVOKEINTERFACE, "me/nallar/whocalled/WhoCalled", "getCallingClass", "(I)Ljava/lang/Class;", true); @@ -169,7 +169,7 @@ public static byte[] rewriteBakedQuad(byte[] originalClass) { methodVisitor.visitLocalVariable("spriteIn", "Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;", null, l0, finalLabel, 4); methodVisitor.visitLocalVariable("applyDiffuseLighting", "Z", null, l0, finalLabel, 5); methodVisitor.visitLocalVariable("format", "Lnet/minecraft/client/renderer/vertex/VertexFormat;", null, l0, finalLabel, 6); - if (LoliConfig.config.logClassesThatNeedPatching) { + if (LoliConfig.getConfig().logClassesThatNeedPatching) { methodVisitor.visitLocalVariable("callee", "Ljava/lang/Class;", null, startingLabel, finalLabel, 7); methodVisitor.visitMaxs(3, 8); } else { diff --git a/src/main/java/zone/rong/loliasm/patches/BakedQuadRetexturedPatch.java b/src/main/java/zone/rong/loliasm/patches/BakedQuadRetexturedPatch.java index ae443ebe..22c148d5 100644 --- a/src/main/java/zone/rong/loliasm/patches/BakedQuadRetexturedPatch.java +++ b/src/main/java/zone/rong/loliasm/patches/BakedQuadRetexturedPatch.java @@ -4,13 +4,13 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import zone.rong.loliasm.LoliTransformer; +import zone.rong.loliasm.core.LoliTransformer; import static org.objectweb.asm.Opcodes.*; /** * This class contains class writers for patched BakedQuadRetextured - * defineClass not called here, pass raw byte[] back to {@link zone.rong.loliasm.LoliTransformer#transform(String, String, byte[])} + * defineClass not called here, pass raw byte[] back to {@link LoliTransformer#transform(String, String, byte[])} * * It is one of the very few instances where BakedQuad actually gets extended. * diff --git a/src/main/java/zone/rong/loliasm/patches/UnpackedBakedQuadPatch.java b/src/main/java/zone/rong/loliasm/patches/UnpackedBakedQuadPatch.java index 1429110e..fc055d33 100644 --- a/src/main/java/zone/rong/loliasm/patches/UnpackedBakedQuadPatch.java +++ b/src/main/java/zone/rong/loliasm/patches/UnpackedBakedQuadPatch.java @@ -4,7 +4,7 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import zone.rong.loliasm.LoliTransformer; +import zone.rong.loliasm.core.LoliTransformer; import static org.objectweb.asm.Opcodes.*; @@ -12,7 +12,7 @@ * TODO Commenting * * This class contains class writers for patched UnpackedBakedQuad and UnpackedBakedQuad$Builder - * defineClass not called here, pass raw byte[] back to {@link zone.rong.loliasm.LoliTransformer#transform(String, String, byte[])} + * defineClass not called here, pass raw byte[] back to {@link LoliTransformer#transform(String, String, byte[])} * * No optimizations here except for soft patching `int tintIndex` to `byte tintIndex` like other BakedQuad derivatives. * Essentially just patching for bytecode compatibility at the moment. diff --git a/src/main/resources/loliasm_at.cfg b/src/main/resources/loliasm_at.cfg new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/mixins.memory.json b/src/main/resources/mixins.memory.json new file mode 100644 index 00000000..6d1c156a --- /dev/null +++ b/src/main/resources/mixins.memory.json @@ -0,0 +1,13 @@ +{ + "package": "zone.rong.loliasm.common.memory.mixins", + "refmap": "mixins.loliasm.refmap.json", + "target": "@env(DEFAULT)", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "LockCodeMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/mixins.recipes.json b/src/main/resources/mixins.recipes.json new file mode 100644 index 00000000..3866ceb3 --- /dev/null +++ b/src/main/resources/mixins.recipes.json @@ -0,0 +1,13 @@ +{ + "package": "zone.rong.loliasm.common.recipes.mixins", + "refmap": "mixins.loliasm.refmap.json", + "target": "@env(DEFAULT)", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "FurnaceRecipesMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/mixins.registries.json b/src/main/resources/mixins.registries.json new file mode 100644 index 00000000..3b674770 --- /dev/null +++ b/src/main/resources/mixins.registries.json @@ -0,0 +1,14 @@ +{ + "package": "zone.rong.loliasm.common.registries.mixins", + "refmap": "mixins.loliasm.refmap.json", + "target": "@env(DEFAULT)", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "RegistrySimpleMixin", + "SoundRegistryMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file