Sign in to follow this  
joedobo

Need advise on a mod for modlauncher, not working.

Recommended Posts

Mod compiles, Ago's modlauncher is able to start server and run the mod, WU runs but it doesn't work as expected.


 


link: https://dl.dropboxusercontent.com/u/27144902/terraformingmod.7z


 


The idea is to make a complete copy of a class and in this case that is com.wurmonline.server.behaviours.Terraforming.


I then make changes to the copy and use it as the source to copy whole methods form using javassist's ctMethod.setbody().


 


My changes to method "dig()" work and dirt is placed on the ground instead of in inventory.


All the changes to isImmutableTile(), isTileOverriddenByDirt(), isTileTurnToDirt(), isCultivatable(), isBuildTile() don't work.


If it worked all tiles would be buildable yet none of them are. If it worked packed dirt, moss, grass, and steppe would all be cultivable yet nothing is.


I've tried a few different things like changing modifiers and it doesn't make a difference.


 


mod source





package com.jase2z.WUmod;


import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.Modifier;

import org.gotti.wurmunlimited.modloader.interfaces.Configurable;
import org.gotti.wurmunlimited.modloader.interfaces.Initable;
import org.gotti.wurmunlimited.modloader.interfaces.WurmMod;
import org.gotti.wurmunlimited.modloader.classhooks.HookException;
import org.gotti.wurmunlimited.modloader.classhooks.HookManager;

import java.util.Properties;


public class TerraformMod implements WurmMod, Initable, Configurable {

public void configure(Properties properties) {
}

public void init() {
try {
CtClass ctTerraforming = HookManager.getInstance().getClassPool().get("com.wurmonline.server.behaviours.Terraforming");
CtClass ctTerraforming1 = HookManager.getInstance().getClassPool().get("com.wurmonline.server.behaviours.Terraforming1");

// next 5 methods are: boolean method(byte)
CtMethod ctIsImmutableTile = ctTerraforming.getMethod("isImmutableTile", "(B)Z");
//ctIsImmutableTile = moddedModifier(ctIsImmutableTile);
CtMethod ctIsImmutableTile1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsImmutableTile.setBody(ctIsImmutableTile1 , null);

CtMethod ctIsTileOverriddenByDirt = ctTerraforming.getMethod("isTileOverriddenByDirt", "(B)Z");
//ctIsTileOverriddenByDirt = moddedModifier(ctIsTileOverriddenByDirt);
CtMethod ctIsTileOverriddenByDirt1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsTileOverriddenByDirt.setBody(ctIsTileOverriddenByDirt1, null);

CtMethod ctIsTileTurnToDirt = ctTerraforming.getMethod("isTileTurnToDirt", "(B)Z");
//ctIsTileTurnToDirt = moddedModifier(ctIsTileTurnToDirt);
CtMethod ctIsTileTurnToDirt1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsTileTurnToDirt.setBody(ctIsTileTurnToDirt1, null);

CtMethod ctIsCultivatable = ctTerraforming.getMethod("isCultivatable", "(B)Z");
//ctIsCultivatable = moddedModifier(ctIsCultivatable);
CtMethod ctIsCultivatable1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsCultivatable.setBody(ctIsCultivatable1, null);

CtMethod ctIsBuildTile = ctTerraforming.getMethod("isBuildTile", "(B)Z");
//ctIsBuildTile = moddedModifier(ctIsBuildTile);
CtMethod ctIsBuildTile1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsBuildTile.setBody(ctIsBuildTile1, null);

//dig(Creature performer, Item source, int tilex, int tiley, int tile, float counter, MeshIO mesh)
//CtClass[] params = new CtClass[]{
//HookManager.getInstance().getClassPool().get("com.wurmonline.server.creatures.Creature"),
//HookManager.getInstance().getClassPool().get("com.wurmonline.server.items.Item"),
//CtClass.intType,
//CtClass.intType,
//CtClass.intType,
//CtClass.floatType,
//HookManager.getInstance().getClassPool().get("com.wurmonline.mesh.MeshIO")
//};
String jvmDescriptor = "(Lcom/wurmonline/server/creatures/Creature;" +
"Lcom/wurmonline/server/items/Item;" +
"IIIF" +
"Lcom/wurmonline/mesh/MeshIO;)Z";
CtMethod ctDig = ctTerraforming.getMethod("dig", jvmDescriptor);
//ctDig = moddedModifier(ctDig);
CtMethod ctDig1 = ctTerraforming1.getMethod("dig", jvmDescriptor);
ctDig.setBody(ctDig1, null);

} catch (NotFoundException | CannotCompileException e) {
throw new HookException(e);
}
}

public static CtMethod moddedModifier(CtMethod method) {
int modifier = method.getModifiers();
if (!Modifier.isPublic(modifier)) {
Modifier.setPublic(modifier);
}
if (Modifier.isFinal(modifier)) {
Modifier.clear(modifier, Modifier.FINAL);
}
method.setModifiers(modifier);
return method;
}
}


 


Thank you for reading and any advice you might have.


 


edit1; added mod source.


 


Edited by joedobo

Share this post


Link to post
Share on other sites

does setBody("return true;") work for the is* methods?

I'm not sure if copying the whole method body works or if there are still references to Terraforming1 in the bytecode. Just a wild guess for now.

Share this post


Link to post
Share on other sites

does setBody("return true;") work for the is* methods?

I'm not sure if copying the whole method body works or if there are still references to Terraforming1 in the bytecode. Just a wild guess for now.

1. I'll try using strings for the simple code. I didn't want to use strings for complex methods like dig.

 

2. I"ll change those "null" values to map values to Terraforming. There weren't any references in the Java code so I thought null would be fine. They could be in the byte code and it won't hurt to do it.

 

Copping the whole body of method "dig()" works. It is a considerably more complex method.

 

Something that would be helpful to me and would likely take less of your time (compared to figuring out why what I'm doing doesn't work) are any suggestion on how I can troubleshoot things in general or even some troubleshoot particular to what I'm doing here. For Python and Pycharm I'd use the IDE debuggers. I can't seem to get something like that to work here.

Edited by joedobo

Share this post


Link to post
Share on other sites

Ok, i took the TerraformMod class, put it in a project. Copied the unmodified source for Terraforming to Terraforming1 and ran the mod. Worked. I ran up to tree and there was cultivate. This is expected since you replace all methods with the isImmutableTile body and tree tiles are immutable..

Then I took your mod and it did not work. This is expectable too considering you're using isImmutableTile everywhere and that's methods body was replaced with

    static final boolean isImmutableTile(final byte type) {        return false;    }
Considerung debuggability I'd suggest a slightly different approach. Instead of copying the methods body it's probably best to call the method from Terraforming1. That way you can add breakpoints into Terraforming1 which actually work.

            ctDig.setBody("return com.wurmonline.server.behaviours.Terraforming1#dig($$);");
(The hash is the javasssist suggested way to reference static methods and fields, $$ is the plain argument list)

Share this post


Link to post
Share on other sites

I made  copy and paste error. I may have to start forcing myself to type things out. The code in the spoiler is what it should be.


 


Yes, with all those "isImmutableTile()" methods copied from terrafroming1 and set into the wrong methods it's not going to work like I thought it should. LIke you said, code wise it was working as expected. It's my fault for giving it the wrong instructions.


 


Ago, thank you again for your effort. 


 





package com.jase2z.WUmod;


import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.Modifier;

import org.gotti.wurmunlimited.modloader.interfaces.Configurable;
import org.gotti.wurmunlimited.modloader.interfaces.Initable;
import org.gotti.wurmunlimited.modloader.interfaces.WurmMod;
import org.gotti.wurmunlimited.modloader.classhooks.HookException;
import org.gotti.wurmunlimited.modloader.classhooks.HookManager;

import java.util.Properties;


public class TerraformMod implements WurmMod, Initable, Configurable {

public void configure(Properties properties) {
}

public void init() {
try {
CtClass ctTerraforming = HookManager.getInstance().getClassPool().get("com.wurmonline.server.behaviours.Terraforming");
CtClass ctTerraforming1 = HookManager.getInstance().getClassPool().get("com.wurmonline.server.behaviours.Terraforming1");

// next 5 methods are: boolean method(byte)
CtMethod ctIsImmutableTile = ctTerraforming.getMethod("isImmutableTile", "(B)Z");
//ctIsImmutableTile = moddedModifier(ctIsImmutableTile);
CtMethod ctIsImmutableTile1 = ctTerraforming1.getMethod("isImmutableTile", "(B)Z");
ctIsImmutableTile.setBody(ctIsImmutableTile1 , null);

CtMethod ctIsTileOverriddenByDirt = ctTerraforming.getMethod("isTileOverriddenByDirt", "(B)Z");
//ctIsTileOverriddenByDirt = moddedModifier(ctIsTileOverriddenByDirt);
CtMethod ctIsTileOverriddenByDirt1 = ctTerraforming1.getMethod("isTileOverriddenByDirt", "(B)Z");
ctIsTileOverriddenByDirt.setBody(ctIsTileOverriddenByDirt1, null);

CtMethod ctIsTileTurnToDirt = ctTerraforming.getMethod("isTileTurnToDirt", "(B)Z");
//ctIsTileTurnToDirt = moddedModifier(ctIsTileTurnToDirt);
CtMethod ctIsTileTurnToDirt1 = ctTerraforming1.getMethod("isTileTurnToDirt", "(B)Z");
ctIsTileTurnToDirt.setBody(ctIsTileTurnToDirt1, null);

CtMethod ctIsCultivatable = ctTerraforming.getMethod("isCultivatable", "(B)Z");
//ctIsCultivatable = moddedModifier(ctIsCultivatable);
CtMethod ctIsCultivatable1 = ctTerraforming1.getMethod("isCultivatable", "(B)Z");
ctIsCultivatable.setBody(ctIsCultivatable1, null);

CtMethod ctIsBuildTile = ctTerraforming.getMethod("isBuildTile", "(B)Z");
//ctIsBuildTile = moddedModifier(ctIsBuildTile);
CtMethod ctIsBuildTile1 = ctTerraforming1.getMethod("isBuildTile", "(B)Z");
ctIsBuildTile.setBody(ctIsBuildTile1, null);

//dig(Creature performer, Item source, int tilex, int tiley, int tile, float counter, MeshIO mesh)
//CtClass[] params = new CtClass[]{
//HookManager.getInstance().getClassPool().get("com.wurmonline.server.creatures.Creature"),
//HookManager.getInstance().getClassPool().get("com.wurmonline.server.items.Item"),
//CtClass.intType,
//CtClass.intType,
//CtClass.intType,
//CtClass.floatType,
//HookManager.getInstance().getClassPool().get("com.wurmonline.mesh.MeshIO")
//};
String jvmDescriptor = "(Lcom/wurmonline/server/creatures/Creature;" +
"Lcom/wurmonline/server/items/Item;" +
"IIIF" +
"Lcom/wurmonline/mesh/MeshIO;)Z";
CtMethod ctDig = ctTerraforming.getMethod("dig", jvmDescriptor);
//ctDig = moddedModifier(ctDig);
CtMethod ctDig1 = ctTerraforming1.getMethod("dig", jvmDescriptor);
ctDig.setBody(ctDig1, null);

} catch (NotFoundException | CannotCompileException e) {
throw new HookException(e);
}
}

public static CtMethod moddedModifier(CtMethod method) {
int modifier = method.getModifiers();
if (!Modifier.isPublic(modifier)) {
Modifier.setPublic(modifier);
}
if (Modifier.isFinal(modifier)) {
Modifier.clear(modifier, Modifier.FINAL);
}
method.setModifiers(modifier);
return method;
}
}


Share this post


Link to post
Share on other sites

Iirc setbody needs a code block where the arguments are given as $1, $2 ect... If you want to change dirt placement wouldn't it be more efficient to look into the code replacement helpers in ago's modloader (using codeattributes). It's not as nice to do as you have to change bytecode but it ensures no errors from decompilers and will work with almost any updates to the dig method as long as they don't change the itemfactory.create part. If you do want to replace the method entirely I'd suggest using the hook manager and make an invocation handler.

Edited by Webba

Share this post


Link to post
Share on other sites

Some methods are just to complex to change them with bytecode alone and Terraforming.dig() uses lots of protected stuff from server.behaviours so it's pretty hard to even move out of the package.

Edited by ago

Share this post


Link to post
Share on other sites

@Webba  


.setbody() is defined in Javassist.CtBehaviour and Javassist.CtMethod. CtMethod also inherits CtBehaviour's .setbody() method. The one that you're referring to and what Ago has used is from CtBehaviour. I used the one from CtMethod.  I have no idea which one is better, it just worked out that I got the one form CtMethod to work first. I'm going to research and experiment to see why.


 


My goal was to experiment with concepts I'm seeing on Javassist tutorial page and documentation. Specifically, that is code injection at class load time so I can completely replace method bodies in server. jar. I believe that a "hook manager and make an invocation handler" are concepts of advanced reflection techniques. I'm not saying that one is better then the other. I don't have a clue. What I do know is in order to learn I need tutorials and controlled experiments.


Share this post


Link to post
Share on other sites

@Webba  

.setbody() is defined in Javassist.CtBehaviour and Javassist.CtMethod. CtMethod also inherits CtBehaviour's .setbody() method. The one that you're referring to and what Ago has used is from CtBehaviour. I used the one from CtMethod.  I have no idea which one is better, it just worked out that I got the one form CtMethod to work first. I'm going to research and experiment to see why.

 

My goal was to experiment with concepts I'm seeing on Javassist tutorial page and documentation. Specifically, that is code injection at class load time so I can completely replace method bodies in server. jar. I believe that a "hook manager and make an invocation handler" are concepts of advanced reflection techniques. I'm not saying that one is better then the other. I don't have a clue. What I do know is in order to learn I need tutorials and controlled experiments.

Ah makes sense, when I was using javassist's setbody to change some methods I was having to supply it with a stringified code block, so say i wanted to change 

void coolFunction(string awesomeString){

system.out.println(awesomeString);

}

to print "string: " + awesomeString 

I would use this

method.setBody("{ system.out.println(\"string : \" + (string)$1 ); }")

$1 is the first parameter $0 is this in the method scope.

 

I may well be doing it wrong but was just following https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html

Edited by Webba

Share this post


Link to post
Share on other sites

I guess you could try making a subclass of terraforming declaring your method there as newDig and setbody to "{MyTerraforming t = new MyTerraforming($0); t.newDig ($args);}"

Edited by Webba

Share this post


Link to post
Share on other sites

The reason I used a whole copied class is so I don't run into problems with private access modifiers in server.jar.  At first I tried to put the methods right in the mod but private modifiers where causing problems. Javassist can set private to public. This makes me wonder if the server.jar would still work if a Javassist program where to permanently converted everything to public. 


 


I'm trying to avoid using approaches that use java code in strings. I loose my IDE tools for that approach. Maybe I could make a script that converts code to strings.


 


My next plan is to limit how much of a method I replace with injection. In the case of Terraforming.dig() there are just a few lines to replace so dirt is created on the ground instead of in inventory. Javassist has ".toString()"  and ".insertAt()" methods that might help me with this. If replace isn't possible I'll add silly logic blocks like if(true == false) {...code I don't want to run...}


 


 


on this page: http://www.javaranch.com/journal/200711/creating_java_classes_runtime_expression_evaluation.html


There is instruction on using ctClass.setSuperclass() with the purpose of "creating a class that extends some other class that has implementations of all kinds of nifty functions"(Ulf Dittmer). I"m not sure if I can use this but it is in agreement with my goal to avoid Java code in strings.


Share this post


Link to post
Share on other sites

This detail is really interesting and helpful to those of us trying to work through extending Wurm.


Thanks for taking the time to write it up.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this