The 'Wand Stability' research node won't give me a research note when I click on it, though it consumes paper and a usage tick on my scribing tools. I'm on Forge 1232, TMachina 0.2.1, and issue occurs on Thaumcraft versions 4.2.1.4 as well as 4.2.3.0. Is this an unfinished feature? I am also using a few other add-ons, but I've never heard of an issue between them that behaves like this. Thanks for your time!
The 'Wand Stability' research node won't give me a research note when I click on it, though it consumes paper and a usage tick on my scribing tools. I'm on Forge 1232, TMachina 0.2.1, and issue occurs on Thaumcraft versions 4.2.1.4 as well as 4.2.3.0. Is this an unfinished feature? I am also using a few other add-ons, but I've never heard of an issue between them that behaves like this. Thanks for your time!
That's an issue with the stub system, that research you should be unlocking automatically when you open up the Wand Augmentation Basics research. However, wand instability is largely not implemented yet, so the information on that is not important just yet.
An update will be out in the upcoming days, I've been off TM development for a bit (been a little busy with other stuff the last few days, and I'm also just not exactly enthusiastic in development in the last few days), I've got stuff to do tomorrow afternoon and I'll be going to bed shortly (1AM :O), so I'll probably do some work on the mod tomorrow morning.
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
Hi! Is there a Thaumcraft API/ Mod API tutorial around? You seem to be pretty able to work with mod API's, would you like to help me :)?
Unfortunately there isn't a tutorial dedicated for working with the Thaumcraft API, or any mod API in general outside of installing said API and loading it into your development environment.
Installing can be as simple as just dropping it in the root of your source folder, which is src/main/java. The best advice I can give you is to just look around in the API's files, see what it gives you. You will need to have a good understanding of how the hierarchy model in Java works, such as interfaces, abstract classes, etc. While the Thaumcraft API does give you the ability to interface with the mod a fair bit, it doesn't give you the level of control to do something like my wand augments purely with the API only. I actually had to do a bit of work with ASM, and had to modify the bytecode of a few Thaumcraft classes to pull that off. This is basically base editing, except without actually directly editing it, and it's a whole lot more complicated, so I'd only recommend using ASM as a last resort, try and find other ways to do what you want before you attempt to use ASM to modify the mod.
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
The Meaning of Life, the Universe, and Everything.
Join Date:
4/9/2014
Posts:
240
Member Details
So you are saying you had to push assembly code to get what you achieved to work? Or is ASM code for something else in Minecraft Modding?
I'm not sure if you are familiar with Visual Studio & C#, but when I use that it has a really good way of browsing referenced assemblies. How would I go about including the referenced mod's API into Eclipse?
Maybe I'm overcomplicating it, but mainly what I'm asking is how do I add a mod I want to load into my Eclipse working version of Minecraft, and how do I browse the mods available methods and objects from Eclipse?
So you are saying you had to push assembly code to get what you achieved to work? Or is ASM code for something else in Minecraft Modding?
I'm not sure if you are familiar with Visual Studio & C#, but when I use that it has a really good way of browsing referenced assemblies. How would I go about including the referenced mod's API into Eclipse?
Maybe I'm overcomplicating it, but mainly what I'm asking is how do I add a mod I want to load into my Eclipse working version of Minecraft, and how do I browse the mods available methods and objects from Eclipse?
Yes, I had to use bytecode injection to inject callbacks to my own methods from the Thaumcraft source. I assume because you brought up assembly, you're familiar with how that process works. Well Java is not compiled down to assembly, but instead compiled down to a "middle man" form of code known as bytecode, which is a stack-oriented low-level language that the JVM (Java Virtual Machine) executes. The JVM is not tied to Java, so any language that works off of the JVM (Scala, Groovy, etc) can theoretically be compiled and executed without actually having the binaries for that language, so if you write Scala programs, your the only user which has to have the Scala binaries installed, AFAIK other users just have to have the JVM installed to execute your compiled Scala program.
ASM is a library which allows a programmer to generate bytecode on the fly, allowing them to say generate entire classes at runtime. ASM also allows a programmer to "watch" over the JVM as it builds the various objects in memory. Using ASM you can create a class transformer, and then ASM will call your transformer each time a class is built, all you need to do is sift through each class you're given and look for one you're interested in (so say a class located at 'thaumcraft.common.items.wands.ItemWandCasting'), then jump the control flow to another method and then sift through that class looking for what method you want to modify, once that's done sift through that method's bytecode until you find a suitable position you want to start injecting your own bytecode (if you're going to do this, I'd recommend decompiling the class and looking at it's bytecode, and work out what you need to modify and where you should start, with ASM you can't say "okay, I want to edit this block of bytecode", you need to iterate over all the bytecode, look at each pair of opcode and operand, and find which one is most suitable for you to start injecting at), then just start injecting.
As I said, in the modding community this is largely viewed as basically just a more advanced version of base editing, but it's the better option if you have no other way of doing something compared to actually base editing, if you choose the perfect injection point your ASM injection should actually survive over updates, I finished coding my ASM class transformer on an early Thaumcraft 4.2 version, and it's up to 4.2.3 and the only thing that has changed in what I've edited is one of the classes was moved, all I had to do was change the signature check so that the class transformer actually goes to edit a class which is actually where it should be. I wouldn't recommend doing this a lot, it is very advanced, and I'd even say more trickier to manage over base edits, it can give you hell, especially because of how Java is compiled down to bytecode, all it takes is one half of the code to be moved down to the next line, and that entire section of bytecode has changed drastically.
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
The Meaning of Life, the Universe, and Everything.
Join Date:
4/9/2014
Posts:
240
Member Details
Really interesting, I wasn't aware that the Java Virtual Machine and Java languages were distinct, or that other languages interpret to themselves into JVM bytecode. So you use ASM when you want to edit the actual classes of Minecraft itself or another mod itself at load time?
What tools do you use to inject bytecode? Does Eclipse or Forge or MC or the JVM actually accept the code?
Really interesting, I wasn't aware that the Java Virtual Machine and Java languages were distinct, or that other languages interpret to themselves into JVM bytecode. So you use ASM when you want to edit the actual classes of Minecraft itself or another mod itself at load time?
What tools do you use to inject bytecode? Does Eclipse or Forge or MC or the JVM actually accept the code?
The ASM library itself actually handles the injection, I'll try and explain what happens.
The source code that you write is sort of like a "template" for the compiler to use. The code you write is not the actual code executed, the compiler takes it and uses it as a template for what it should generate as bytecode. When you run a class file, the JVM essentially takes that bytecode and loads it into memory, it's at this point that you use a class transformer to inject your own bytecode.
This section is about how you can use ASM with Minecraft, and how I used it, the next section following the next bold line is about the JVM and the languages that build off of it.
I'll paste my class transformer source code here (hopefully it doesn't go against the Thaumcraft license, I don't think it will) in a spoiler and I'll explain what it's doing, but firstly I need to explain what Forge, more specifically ForgeModLoader has to do with this.
ASM is not a Forge library, it's a publicly licensed open source (I think) library which is available largely for anyone to use. FML uses ASM partially during the loading procedure, if you've heard the term access transformer, or coremod being thrown about, these two use ASM (an access transformer (AT) uses ASM to change an objects access modifier where reflection cannot be used, and a coremod was basically like a super advanced mod type in FML that allowed the mod to load WAY before other mods loaded, thus it allowed developers to register ATs, class transformers, etc. An AT is an FML thing, and it appears that an IClassTransformer is a vanilla Minecraft thing, apparently from my import statement(s)) heavily.
Anyways, here's my class transformer source code, note that I do have another class implementing IFMLLoadingPlugin. FML will use reflection and the Google Java library to iterate over all loaded classes, this is how FML can find your @Mod class, and your other classes, without you actually telling it where to find it. FML does this with IFMLLoadingPlugin's as well. The other class is just set up to register my class transformer.
package jcm2606.thaumicmachina.asm;
import java.util.Iterator;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
public class TMClassTransformer implements IClassTransformer
{
String itemStackClassPath;
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if (name.equals("thaumcraft.common.items.wands.ItemWandCasting"))
{
this.itemStackClassPath = ItemStack.class.getName().replace(".", "/");
TMFMLLoadingPlugin.log("Found Thaumcraft base class 'ItemWandCasting', patching class.", "INFO");
basicClass = this.patchItemWandCastingClass(name, basicClass);
}
if (name.equals("thaumcraft.client.renderers.models.gear.ModelWand")
|| name.equals("thaumcraft.client.renderers.models.ModelWand"))
{
TMFMLLoadingPlugin.log("Found Thaumcraft base class 'ModelWand', patching class.", "INFO");
basicClass = this.patchModelWandClass(name, basicClass);
}
return basicClass;
}
public byte[] patchItemWandCastingClass(String className, byte[] bytes)
{
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(bytes);
reader.accept(node, 0);
Iterator methodIterator = node.methods.iterator();
TMFMLLoadingPlugin.log("Inside 'ItemWandCasting' class, searching for methods...", "INFO");
while (methodIterator.hasNext())
{
MethodNode mnode = methodIterator.next();
int index = -1;
if (mnode.name.equals("getMaxVis") && mnode.desc.equals("(L" + this.itemStackClassPath + ";)I"))
{
TMFMLLoadingPlugin.log("Discovered 'getMaxVis' method, patching method...", "INFO");
AbstractInsnNode currNode = null;
AbstractInsnNode targetNode = null;
Iterator iterator = mnode.instructions.iterator();
int i = -1;
while (iterator.hasNext())
{
i += 1;
currNode = iterator.next();
if (currNode.getOpcode() == Opcodes.IFEQ)
{
targetNode = currNode;
index = i - 6;
}
}
mnode.instructions.remove(mnode.instructions.get(index + 14));
mnode.instructions.remove(mnode.instructions.get(index + 13));
mnode.instructions.remove(mnode.instructions.get(index + 12));
mnode.instructions.remove(mnode.instructions.get(index + 11));
mnode.instructions.remove(mnode.instructions.get(index + 10));
mnode.instructions.remove(mnode.instructions.get(index + 9));
mnode.instructions.remove(mnode.instructions.get(index + 8));
mnode.instructions.remove(mnode.instructions.get(index + 7));
mnode.instructions.remove(mnode.instructions.get(index + 6));
mnode.instructions.remove(mnode.instructions.get(index + 5));
mnode.instructions.remove(mnode.instructions.get(index + 4));
mnode.instructions.remove(mnode.instructions.get(index + 3));
mnode.instructions.remove(mnode.instructions.get(index + 2));
mnode.instructions.remove(mnode.instructions.get(index + 1));
mnode.instructions.remove(mnode.instructions.get(index - 1));
InsnList injectionList = new InsnList();
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler", "handleMaxVisMethod", "(L"
+ this.itemStackClassPath + ";)I"));
mnode.instructions.insert(mnode.instructions.get(index - 1), injectionList);
TMFMLLoadingPlugin.log("Patched method.", "INFO");
}
if (mnode.name.equals("hasRunes") && mnode.desc.equals("(L" + this.itemStackClassPath + ";)Z"))
{
TMFMLLoadingPlugin.log("Discovered 'hasRunes' method, patching method...", "INFO");
AbstractInsnNode currNode = null;
AbstractInsnNode targetNode = null;
Iterator iterator = mnode.instructions.iterator();
int i = -1;
while (iterator.hasNext())
{
i += 1;
currNode = iterator.next();
if (currNode.getOpcode() == Opcodes.ICONST_0)
{
targetNode = currNode;
index = i - 1;
}
}
mnode.instructions.remove(mnode.instructions.get(index + 1));
InsnList injectionList = new InsnList();
injectionList.add(new VarInsnNode(Opcodes.ALOAD, 1));
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler", "handleHasRunesMethod", "(L"
+ this.itemStackClassPath + ";)Z"));
mnode.instructions.insert(mnode.instructions.get(index), injectionList);
TMFMLLoadingPlugin.log("Patched method.", "INFO");
}
if (mnode.name.equals("getConsumptionModifier")
&& mnode.desc.equals("(L" + this.itemStackClassPath + ";" + "L"
+ EntityPlayer.class.getName().replace(".", "/")
+ ";Lthaumcraft/api/aspects/Aspect;Z)F"))
{
TMFMLLoadingPlugin.log("Discovered 'getConsumptionModifier' method, patching method...",
"INFO");
AbstractInsnNode currNode = null;
AbstractInsnNode targetNode = null;
// First patch
Iterator iterator = mnode.instructions.iterator();
int i = -1;
while (iterator.hasNext())
{
i += 1;
currNode = iterator.next();
if (currNode.getOpcode() == Opcodes.IFEQ)
{
targetNode = currNode;
index = i + 2;
break;
}
}
mnode.instructions.remove(mnode.instructions.get(index + 4));
mnode.instructions.remove(mnode.instructions.get(index + 3));
mnode.instructions.remove(mnode.instructions.get(index + 2));
mnode.instructions.remove(mnode.instructions.get(index + 1));
InsnList injectionList = new InsnList();
injectionList.add(new VarInsnNode(Opcodes.ALOAD, 1));
injectionList.add(new InsnNode(Opcodes.ICONST_1));
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler",
"handleConsumptionModifierMethod", "(L" + this.itemStackClassPath + ";Z)F"));
mnode.instructions.insert(mnode.instructions.get(index), injectionList);
// Second patch
iterator = mnode.instructions.iterator();
i = -1;
while (iterator.hasNext())
{
i += 1;
currNode = iterator.next();
if (currNode instanceof FrameNode)
{
targetNode = currNode;
index = i;
break;
}
}
mnode.instructions.remove(mnode.instructions.get(index + 4));
mnode.instructions.remove(mnode.instructions.get(index + 3));
mnode.instructions.remove(mnode.instructions.get(index + 2));
mnode.instructions.remove(mnode.instructions.get(index + 1));
injectionList = new InsnList();
injectionList.add(new VarInsnNode(Opcodes.ALOAD, 1));
injectionList.add(new InsnNode(Opcodes.ICONST_0));
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler",
"handleConsumptionModifierMethod", "(L" + this.itemStackClassPath + ";Z)F"));
mnode.instructions.insert(mnode.instructions.get(index), injectionList);
TMFMLLoadingPlugin.log("Patched method.", "INFO");
}
}
TMFMLLoadingPlugin.log("Patched class.", "INFO");
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
node.accept(writer);
return writer.toByteArray();
}
public byte[] patchModelWandClass(String className, byte[] bytes)
{
ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(bytes);
reader.accept(node, 0);
Iterator methodIterator = node.methods.iterator();
TMFMLLoadingPlugin.log("Inside 'ModelWand' class, searching for methods...", "INFO");
while (methodIterator.hasNext())
{
MethodNode mnode = methodIterator.next();
int index = -1;
if (mnode.name.equals("render") && mnode.desc.equals("(L" + this.itemStackClassPath + ";)V"))
{
TMFMLLoadingPlugin.log("Discovered 'render' method, patching method...", "INFO");
AbstractInsnNode currNode = null;
AbstractInsnNode targetNode = null;
Iterator iterator = mnode.instructions.iterator();
int i = -1;
while (iterator.hasNext())
{
i += 1;
currNode = iterator.next();
if (currNode instanceof LdcInsnNode)
{
LdcInsnNode ldcNode = (LdcInsnNode) currNode;
if (ldcNode.cst instanceof Double)
{
double d = (double) ldcNode.cst;
if (d == -0.009999999776482582)
{
targetNode = currNode;
index = i - 8;
}
}
}
}
mnode.instructions.remove(mnode.instructions.get(index + 2));
InsnList injectionList = new InsnList();
injectionList.add(new VarInsnNode(Opcodes.ALOAD, 1));
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler", "handleRuneRendererDouble",
"(L" + this.itemStackClassPath + ";)D"));
mnode.instructions.insert(mnode.instructions.get(index + 1), injectionList);
index -= 14;
mnode.instructions.remove(mnode.instructions.get(index + 1));
injectionList = new InsnList();
injectionList.add(new VarInsnNode(Opcodes.ALOAD, 1));
injectionList.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"jcm2606/thaumicmachina/wand/WandAugmentationHandler", "handleRuneRendererPasses",
"(L" + this.itemStackClassPath + ";)I"));
mnode.instructions.insert(mnode.instructions.get(index), injectionList);
TMFMLLoadingPlugin.log("Patched method.", "INFO");
break;
}
}
TMFMLLoadingPlugin.log("Patched class.", "INFO");
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
node.accept(writer);
return writer.toByteArray();
}
}
Okay, now to explain. Basically, what's happening is this. That transform(...) method is the IClassTransformer's method, and that gets called every time a class is loaded by the JVM. Based on the parameters, you can see what each one is, but I'll explain them anyways. The first String parameter is the classes name, this is the classpath name, so it isn't just ItemStack, or Item, or whatever, it actually is net.minecraft.item.ItemStack, or net.minecraft.item.Item, etc. The next one I don't know what it is, but AFAIK it has the same value as the first parameter. And the third parameter is a byte array. The spoiler below explains why it's of the type byte, if you already know you can skip it.
The low level code that the JVM works with is known as bytecode, and can you guess why it has byte in the name? It's called that because that entire instruction (including it's value(s), or operand(s) formally speaking) is all held within a single byte, sometimes two depending on the values the instruction, or opcode, needs.
So, that transform(...) method is called whenever a class is loaded into the JVM, and you are given the basic details of that class, including it's classpath name, transformed name (whatever that is) and all of it's bytecode. Essentially what I do then is I put the class' name up against several if statements to filter what I'm receiving, because I only want to edit a hand full of classes, not all of them, so I only jump to the editing methods if the current class is a class I'm interested in. If it's a class I'm not interested in, I just return the bytecode without touching it. By the way, the byte array you return is the edited bytecode.
Okay, now once I've got a class I'm interested in, I then create a ClassNode instance (ASM handles things in sort a node fashion, so a ClassNode is basically an object representing a class, and the node holds all the other nodes for methods, fields, instructions, etc) and a ClassReader instance, and I set the class reader to use the class node instance I created. Once that's done I create and Iterator for the method nodes, and enter a while loop with the condition set to if the iterator has another element. Now, as I said earlier, with ASM you can't just tell it what method you want to edit and then get it to give you a method node back representing that method, you have to sift through all the nodes in the set you're given and find ones that interest you, and this is exactly what I do next. Once I'm in the while loop, I iterate over the set and use if statements to find nodes that I'm interested in, so far it has been simple, but now it gets trickier.
Now I need to find a good point to start injecting bytecode, the closer I am to the spot I want to inject, the better I am overall, but the catch is there are a many instructions that have the same opcode, you'll see some bytecode examples in a bit. So I can't exactly just look for an instruction, I also need to look at the operands, or values, paired up with the opcode. So what I do is iterate over all instruction nodes, and look for one that I'm interested in, once I've found it I store the index in the set of nodes.
Next comes that actual injection! The simplest way of manipulating bytecode is to remove instructions, and it's as simple as removing the instruction node from the set of instruction nodes that the method node has. The only thing to remember is the list is updating in real time, let's have an example. Okay, so say we were searching for the instruction node that we wish to start editing at (so the node we're interested in during iteration is the starting node that we wish to edit), and we want to edit the next 3 nodes after that. We can either edit from the top down, so start at the start, or from the bottom up, so start at the end and work our way to the start. I'd choose starting from the bottom, and here's why. When we remove instructions, we remove them based on the index. If we were starting from the top down, we'd firstly remove the starting node, which would push the rest up, then the next node, which is in the same position, then the next, etc. While this is easy because we don't need to change the index, it's harder to visualise how it will work during runtime (which, you need to visualise it, this isn't as easy as removing something from a List, in a List you can largely control what's stored, in a node list you may have a node crucially important to your class, such as a label statement or something, you'll see an example). Starting from the bottom up is worlds easier to visualise, at least for me.
Adding instructions is also simple, you just need to build an InsnList, and add your bytecode instructions to the list in the order they must go. You must make sure they match, so if you remove a return instruction, add it back in, don't leave it out else you will crash the JVM. Then just insert your instruction list into the method node's instruction list at an index that best suits your custom bytecode, and voila, you have injected code into the method using bytecode manipulation. You are not done yet though...
Finally, you create a ClassWriter instance, using the same parameters I use (honestly I'm not sure what they're for, but I do know what frames are, they're too complicated to explain here, especially with how long this message is. Then just tell the node to accept the writer, and return writer.toByteArray(). Done! Class transformer written!
The bytecode examples will be at the end of the post in a spoiler.
Now onto how the JVM works with languages.
Okay, so the JVM is basically like a virtual CPU, sort of. It takes instructions in, as a form that is similar to assembly (bytecode) and executes them at a low level. Java and the JVM are basically the same, however technically speaking they are different. The JVM is designed to work with Java syntax and source files, however some other languages can compile down into JVM bytecode. Scala being the best example as it is supported for Minecraft mod development.
Scala is not directly translated into JVM bytecode AFAIK, there are a few steps to take when compiling, however the finished product is indeed JVM bytecode, Scala is actually designed to work specifically for the JVM. Because of Scala's interaction with the JVM, it is basically directly compatible with Java libraries (fun fact, Scala actually borrows Java's classes, for example, Scala does not have it's own String class, it instead uses Java's). On the outside, it is very much safe to say Scala and Java are different, Scala is basically Java on steroids, what it does is black magic and is freaking amazing (anonymous functions are amazing) (thank you to a friend who pointed me to Scala).
There are a few other languages that are compiled into JVM bytecode, some of them being Groovy, Kawa, Fantom, Clojure and BBj.
Links
Here are a few links you should read up on if you're interested on the JVM, Scala, ASM, or bytecode in general.
What's a cat program? IIRC a Cat program is a simple program which takes a users input, and repeats that input back to the user. I will use a bit more logic in mine, so that it will constantly loop until the user enters 'exit'.
Java Source Code:
boolean running = true;
Scanner in = new Scanner(System.in);
while (running)
{
String input = in.nextLine();
System.out.println(input);
if (input.equalsIgnoreCase("exit"))
{
running = false;
}
}
hey dont know if you guys are looking for bug reports but I have one here. I was using thaumic Machines and when I tried to activate the infusion for bone wand core with a iron capped wood wand it crashed the server. here is the link to the crash report for you http://pastebin.com/v2PF08va
hey dont know if you guys are looking for bug reports but I have one here. I was using thaumic Machines and when I tried to activate the infusion for bone wand core with a iron capped wood wand it crashed the server. here is the link to the crash report for you http://pastebin.com/v2PF08va
hope you see this, cause its a great mod
Try updating the mod, that bug was caused by Azanor changing one of the methods around (in the version I was working with it was static, the public builds were not static, so my mod was calling it incorrectly. I fixed it by implementing my own method).
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
I found a bug, the Wand Stability research doesn't give a research note, nor does it unlock by clicking on it.
PS In the pictures posted theres a white victus symbol research to the right of the wand augment research, it doesn't show up when I use a give all research command. What is it, cut/not implemented content?
PSS Under Tainted Core, it says 'ability to work with darker, more sinister magic.' What is this dark magic? Is it implamented yet? And if so how do you use it?
I found a bug, the Wand Stability research doesn't give a research note, nor does it unlock by clicking on it.
PS In the pictures posted theres a white victus symbol research to the right of the wand augment research, it doesn't show up when I use a give all research command. What is it, cut/not implemented content?
PSS Under Tainted Core, it says 'ability to work with darker, more sinister magic.' What is this dark magic? Is it implamented yet? And if so how do you use it?
Wand Stability should be given to you when you unlock the base augment research, I have to look into why it isn't functioning properly.
Those were removed as they were clutter, the ideas I had in place there simply were too thin to add in at this point, so I've decided to roll it up into other trees.
That's a hint as to how you can now use darker mechanics with that wand of your's. In all seriousness, it's an augment that is going to open up the world of dark magic to you without you needing to make a specific wand; I plan on locking dark magic to specific wand types. Largely this is unimplemented. Currently it just allows you to add other tainted augments to your wand; tainted augments require your wand to either be compatible or have that augment installed.
Currently TM is on hold for a little bit, I have exams in school and I also have assignments due, I'll probably get some work done on it over the upcoming holidays; roughly in about 3 weeks. School before modding guys, school before modding.
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
Interesting looking mod! Jcm, just wondering if you're aware that "Thaumic" is spelt incorrectly in your logo image. Sorry if this has been mentioned before, but I did have a quick scan over the thread.
I'm aware, my texturer didn't feel like fixing it and if I attempt to I may mess it up. So for now I'm rolling with it being a "secret".
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
Also, I saw an unfinished wand core, the Celestial Wand Core, in the creative tab, too. Can you tell me what that will be like, or is it too unfinished?
Also, I saw an unfinished wand core, the Celestial Wand Core, in the creative tab, too. Can you tell me what that will be like, or is it too unfinished?
Unfinished.
But basically this is my idea for the mod; there's a whole new tech tier introduced in Thaumcraft that the Crimson Rites gives you access to, this tree has to do with altering the universe and all such things. One of the things I was thinking of was energy for this new tech tier, I wanted to make wands a large component of this but wands seem unbalanced contrasted with the power a player can have with the new tech tier; hence the Celestial core was thought up.
Essentially the Celestial core is a wand core that is infused with the raw celestial power of an Aura Node, so the Celestial core will be able to store a LOT of vis for the player to use; however, the player cannot use the Celestial core like a normal wand, it's used as a "battery" for other machinery. One of them I'm thinking of is the ability to tear a node open and actually utilise the world on the other side of the node, maybe to create mystical barriers or teleportation; I don't know yet.
BUT, I've just finished my round of exams for the HSC, all I need to do is finish off one assignment then I have a week of school left, then holidays, so I will have time to work on the mod!
Rollback Post to RevisionRollBack
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
But basically this is my idea for the mod; there's a whole new tech tier introduced in Thaumcraft that the Crimson Rites gives you access to, this tree has to do with altering the universe and all such things. One of the things I was thinking of was energy for this new tech tier, I wanted to make wands a large component of this but wands seem unbalanced contrasted with the power a player can have with the new tech tier; hence the Celestial core was thought up.
Essentially the Celestial core is a wand core that is infused with the raw celestial power of an Aura Node, so the Celestial core will be able to store a LOT of vis for the player to use; however, the player cannot use the Celestial core like a normal wand, it's used as a "battery" for other machinery. One of them I'm thinking of is the ability to tear a node open and actually utilise the world on the other side of the node, maybe to create mystical barriers or teleportation; I don't know yet.
BUT, I've just finished my round of exams for the HSC, all I need to do is finish off one assignment then I have a week of school left, then holidays, so I will have time to work on the mod!
Glad to hear that school is going well and the whole "opening a node" idea literally made me wanna burst with excitement. The idea of controlling a node like that sounds amazing to me so I really hope that all comes together.
That's an issue with the stub system, that research you should be unlocking automatically when you open up the Wand Augmentation Basics research. However, wand instability is largely not implemented yet, so the information on that is not important just yet.
An update will be out in the upcoming days, I've been off TM development for a bit (been a little busy with other stuff the last few days, and I'm also just not exactly enthusiastic in development in the last few days), I've got stuff to do tomorrow afternoon and I'll be going to bed shortly (1AM :O), so I'll probably do some work on the mod tomorrow morning.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
Unfortunately there isn't a tutorial dedicated for working with the Thaumcraft API, or any mod API in general outside of installing said API and loading it into your development environment.
Installing can be as simple as just dropping it in the root of your source folder, which is src/main/java. The best advice I can give you is to just look around in the API's files, see what it gives you. You will need to have a good understanding of how the hierarchy model in Java works, such as interfaces, abstract classes, etc. While the Thaumcraft API does give you the ability to interface with the mod a fair bit, it doesn't give you the level of control to do something like my wand augments purely with the API only. I actually had to do a bit of work with ASM, and had to modify the bytecode of a few Thaumcraft classes to pull that off. This is basically base editing, except without actually directly editing it, and it's a whole lot more complicated, so I'd only recommend using ASM as a last resort, try and find other ways to do what you want before you attempt to use ASM to modify the mod.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
I'm not sure if you are familiar with Visual Studio & C#, but when I use that it has a really good way of browsing referenced assemblies. How would I go about including the referenced mod's API into Eclipse?
Maybe I'm overcomplicating it, but mainly what I'm asking is how do I add a mod I want to load into my Eclipse working version of Minecraft, and how do I browse the mods available methods and objects from Eclipse?
Yes, I had to use bytecode injection to inject callbacks to my own methods from the Thaumcraft source. I assume because you brought up assembly, you're familiar with how that process works. Well Java is not compiled down to assembly, but instead compiled down to a "middle man" form of code known as bytecode, which is a stack-oriented low-level language that the JVM (Java Virtual Machine) executes. The JVM is not tied to Java, so any language that works off of the JVM (Scala, Groovy, etc) can theoretically be compiled and executed without actually having the binaries for that language, so if you write Scala programs, your the only user which has to have the Scala binaries installed, AFAIK other users just have to have the JVM installed to execute your compiled Scala program.
ASM is a library which allows a programmer to generate bytecode on the fly, allowing them to say generate entire classes at runtime. ASM also allows a programmer to "watch" over the JVM as it builds the various objects in memory. Using ASM you can create a class transformer, and then ASM will call your transformer each time a class is built, all you need to do is sift through each class you're given and look for one you're interested in (so say a class located at 'thaumcraft.common.items.wands.ItemWandCasting'), then jump the control flow to another method and then sift through that class looking for what method you want to modify, once that's done sift through that method's bytecode until you find a suitable position you want to start injecting your own bytecode (if you're going to do this, I'd recommend decompiling the class and looking at it's bytecode, and work out what you need to modify and where you should start, with ASM you can't say "okay, I want to edit this block of bytecode", you need to iterate over all the bytecode, look at each pair of opcode and operand, and find which one is most suitable for you to start injecting at), then just start injecting.
As I said, in the modding community this is largely viewed as basically just a more advanced version of base editing, but it's the better option if you have no other way of doing something compared to actually base editing, if you choose the perfect injection point your ASM injection should actually survive over updates, I finished coding my ASM class transformer on an early Thaumcraft 4.2 version, and it's up to 4.2.3 and the only thing that has changed in what I've edited is one of the classes was moved, all I had to do was change the signature check so that the class transformer actually goes to edit a class which is actually where it should be. I wouldn't recommend doing this a lot, it is very advanced, and I'd even say more trickier to manage over base edits, it can give you hell, especially because of how Java is compiled down to bytecode, all it takes is one half of the code to be moved down to the next line, and that entire section of bytecode has changed drastically.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
What tools do you use to inject bytecode? Does Eclipse or Forge or MC or the JVM actually accept the code?
The ASM library itself actually handles the injection, I'll try and explain what happens.
The source code that you write is sort of like a "template" for the compiler to use. The code you write is not the actual code executed, the compiler takes it and uses it as a template for what it should generate as bytecode. When you run a class file, the JVM essentially takes that bytecode and loads it into memory, it's at this point that you use a class transformer to inject your own bytecode.
This section is about how you can use ASM with Minecraft, and how I used it, the next section following the next bold line is about the JVM and the languages that build off of it.
I'll paste my class transformer source code here (hopefully it doesn't go against the Thaumcraft license, I don't think it will) in a spoiler and I'll explain what it's doing, but firstly I need to explain what Forge, more specifically ForgeModLoader has to do with this.
ASM is not a Forge library, it's a publicly licensed open source (I think) library which is available largely for anyone to use. FML uses ASM partially during the loading procedure, if you've heard the term access transformer, or coremod being thrown about, these two use ASM (an access transformer (AT) uses ASM to change an objects access modifier where reflection cannot be used, and a coremod was basically like a super advanced mod type in FML that allowed the mod to load WAY before other mods loaded, thus it allowed developers to register ATs, class transformers, etc. An AT is an FML thing, and it appears that an IClassTransformer is a vanilla Minecraft thing, apparently from my import statement(s)) heavily.
Anyways, here's my class transformer source code, note that I do have another class implementing IFMLLoadingPlugin. FML will use reflection and the Google Java library to iterate over all loaded classes, this is how FML can find your @Mod class, and your other classes, without you actually telling it where to find it. FML does this with IFMLLoadingPlugin's as well. The other class is just set up to register my class transformer.
The low level code that the JVM works with is known as bytecode, and can you guess why it has byte in the name? It's called that because that entire instruction (including it's value(s), or operand(s) formally speaking) is all held within a single byte, sometimes two depending on the values the instruction, or opcode, needs.
Okay, now once I've got a class I'm interested in, I then create a ClassNode instance (ASM handles things in sort a node fashion, so a ClassNode is basically an object representing a class, and the node holds all the other nodes for methods, fields, instructions, etc) and a ClassReader instance, and I set the class reader to use the class node instance I created. Once that's done I create and Iterator for the method nodes, and enter a while loop with the condition set to if the iterator has another element. Now, as I said earlier, with ASM you can't just tell it what method you want to edit and then get it to give you a method node back representing that method, you have to sift through all the nodes in the set you're given and find ones that interest you, and this is exactly what I do next. Once I'm in the while loop, I iterate over the set and use if statements to find nodes that I'm interested in, so far it has been simple, but now it gets trickier.
Now I need to find a good point to start injecting bytecode, the closer I am to the spot I want to inject, the better I am overall, but the catch is there are a many instructions that have the same opcode, you'll see some bytecode examples in a bit. So I can't exactly just look for an instruction, I also need to look at the operands, or values, paired up with the opcode. So what I do is iterate over all instruction nodes, and look for one that I'm interested in, once I've found it I store the index in the set of nodes.
Next comes that actual injection! The simplest way of manipulating bytecode is to remove instructions, and it's as simple as removing the instruction node from the set of instruction nodes that the method node has. The only thing to remember is the list is updating in real time, let's have an example. Okay, so say we were searching for the instruction node that we wish to start editing at (so the node we're interested in during iteration is the starting node that we wish to edit), and we want to edit the next 3 nodes after that. We can either edit from the top down, so start at the start, or from the bottom up, so start at the end and work our way to the start. I'd choose starting from the bottom, and here's why. When we remove instructions, we remove them based on the index. If we were starting from the top down, we'd firstly remove the starting node, which would push the rest up, then the next node, which is in the same position, then the next, etc. While this is easy because we don't need to change the index, it's harder to visualise how it will work during runtime (which, you need to visualise it, this isn't as easy as removing something from a List, in a List you can largely control what's stored, in a node list you may have a node crucially important to your class, such as a label statement or something, you'll see an example). Starting from the bottom up is worlds easier to visualise, at least for me.
Adding instructions is also simple, you just need to build an InsnList, and add your bytecode instructions to the list in the order they must go. You must make sure they match, so if you remove a return instruction, add it back in, don't leave it out else you will crash the JVM. Then just insert your instruction list into the method node's instruction list at an index that best suits your custom bytecode, and voila, you have injected code into the method using bytecode manipulation. You are not done yet though...
Finally, you create a ClassWriter instance, using the same parameters I use (honestly I'm not sure what they're for, but I do know what frames are, they're too complicated to explain here, especially with how long this message is. Then just tell the node to accept the writer, and return writer.toByteArray(). Done! Class transformer written!
The bytecode examples will be at the end of the post in a spoiler.
Now onto how the JVM works with languages.
Okay, so the JVM is basically like a virtual CPU, sort of. It takes instructions in, as a form that is similar to assembly (bytecode) and executes them at a low level. Java and the JVM are basically the same, however technically speaking they are different. The JVM is designed to work with Java syntax and source files, however some other languages can compile down into JVM bytecode. Scala being the best example as it is supported for Minecraft mod development.
Scala is not directly translated into JVM bytecode AFAIK, there are a few steps to take when compiling, however the finished product is indeed JVM bytecode, Scala is actually designed to work specifically for the JVM. Because of Scala's interaction with the JVM, it is basically directly compatible with Java libraries (fun fact, Scala actually borrows Java's classes, for example, Scala does not have it's own String class, it instead uses Java's). On the outside, it is very much safe to say Scala and Java are different, Scala is basically Java on steroids, what it does is black magic and is freaking amazing (anonymous functions are amazing) (thank you to a friend who pointed me to Scala).
There are a few other languages that are compiled into JVM bytecode, some of them being Groovy, Kawa, Fantom, Clojure and BBj.
Links
Here are a few links you should read up on if you're interested on the JVM, Scala, ASM, or bytecode in general.
ASM: ASM 2.0 Tutorial
JVM: JVM Wiki Page, JVM Specs
JVM Bytecode: JVM Bytecode Wiki Page, JVM Bytecode Instruction Listings, The fundamentals of JVM bytecode.
Bytecode Examples
Java Source Code:
Bytecode
Java Source Code:
Bytecode
What's a cat program? IIRC a Cat program is a simple program which takes a users input, and repeats that input back to the user. I will use a bit more logic in mine, so that it will constantly loop until the user enters 'exit'.
Java Source Code:
Bytecode
As you can see, with bytecode, simple code can become quite complex.
EDIT: Sorry for the delayed response, huuuuuuge text wall to write out.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
hope you see this, cause its a great mod
Try updating the mod, that bug was caused by Azanor changing one of the methods around (in the version I was working with it was static, the public builds were not static, so my mod was calling it incorrectly. I fixed it by implementing my own method).
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
Tutorial Series With MCDeathslimbs!
I found a bug, the Wand Stability research doesn't give a research note, nor does it unlock by clicking on it.
PS In the pictures posted theres a white victus symbol research to the right of the wand augment research, it doesn't show up when I use a give all research command. What is it, cut/not implemented content?
PSS Under Tainted Core, it says 'ability to work with darker, more sinister magic.' What is this dark magic? Is it implamented yet? And if so how do you use it?
Wand Stability should be given to you when you unlock the base augment research, I have to look into why it isn't functioning properly.
Those were removed as they were clutter, the ideas I had in place there simply were too thin to add in at this point, so I've decided to roll it up into other trees.
That's a hint as to how you can now use darker mechanics with that wand of your's. In all seriousness, it's an augment that is going to open up the world of dark magic to you without you needing to make a specific wand; I plan on locking dark magic to specific wand types. Largely this is unimplemented. Currently it just allows you to add other tainted augments to your wand; tainted augments require your wand to either be compatible or have that augment installed.
Currently TM is on hold for a little bit, I have exams in school and I also have assignments due, I'll probably get some work done on it over the upcoming holidays; roughly in about 3 weeks. School before modding guys, school before modding.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
I'm aware, my texturer didn't feel like fixing it and if I attempt to I may mess it up. So for now I'm rolling with it being a "secret".
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
Hmm... Its a secret to everybody.
Also, what's up with the Node Modifications in the creative tab for this mod? I can't find research or anything in the Thaumonomicon for it.
It's not 100% finished.
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
Well, then I look forward to its completion!
Also, I saw an unfinished wand core, the Celestial Wand Core, in the creative tab, too. Can you tell me what that will be like, or is it too unfinished?
Unfinished.
But basically this is my idea for the mod; there's a whole new tech tier introduced in Thaumcraft that the Crimson Rites gives you access to, this tree has to do with altering the universe and all such things. One of the things I was thinking of was energy for this new tech tier, I wanted to make wands a large component of this but wands seem unbalanced contrasted with the power a player can have with the new tech tier; hence the Celestial core was thought up.
Essentially the Celestial core is a wand core that is infused with the raw celestial power of an Aura Node, so the Celestial core will be able to store a LOT of vis for the player to use; however, the player cannot use the Celestial core like a normal wand, it's used as a "battery" for other machinery. One of them I'm thinking of is the ability to tear a node open and actually utilise the world on the other side of the node, maybe to create mystical barriers or teleportation; I don't know yet.
BUT, I've just finished my round of exams for the HSC, all I need to do is finish off one assignment then I have a week of school left, then holidays, so I will have time to work on the mod!
Author of the Clarity, Serenity, Sapphire & Halcyon shader packs for Minecraft: Java Edition.
My Github page.
The entire Minecraft shader development community now has its own Discord server! Feel free to join and chat with all the developers!
Glad to hear that school is going well and the whole "opening a node" idea literally made me wanna burst with excitement. The idea of controlling a node like that sounds amazing to me so I really hope that all comes together.