Thank you. It solves the problem I had. I knew it was something like this but not being familiar with Netbeans, I didn't do it correctly .
I'll add theses informations to the first post.
Quote from OpticalDelusion »
I like the idea here. The main problem is, at least for me, I have trouble finding which classes I want to modify due to the obfuscation. It is so inconvenient every update for me to go through all the classes to find what I want. If there was an updated list that was collaboratively updated that would be fantastic. Of course, I feel like Notch would frown upon such a practice.
- OpticalDelusion
I think it would not be against the forum and game rules to do such things.
If so, i'll keep a list at the top of the thread...
(Have to read forum and game agreement before just to be certain of that )
Some classes have already been mentionned in the first page ( Server classes and Client class one post later)
I've been having a quick browse around in the code today; figured I'd use this post as a home for any findings. A public reference list for myself, if you will - who knows, maybe other people will find it useful. I provide no guarantee my findings are correct.
Assumed decompilation is done with Java Decompiler (JD).
Minecraft Alpha v1.1.2_01
oj.class - Generic superclass for Food Methods public oj(int paramInt1, int paramInt2)
paramInt1 - Passed to di superclass. Appears to be the Item ID.
paramInt2 - Sets attribute 'a'. Unsure of purpose; Does not appear to be stack-size. Requires further testing. Hearts Restored * 2.
Analysis / Findings:
Debugging has revealed this class is instantiated at login. Initial analysis appears to indicate paramInt2 determines how much health the item heals:
Debug: Item<4> - paramInt2: 4 // Apples heals 2.
Debug: Item<26> - paramInt2: 10 // Mushroom soup heals 5.
Debug: Item<41> - paramInt2: 5 // Bread heals 10
Debug: Item<63> - paramInt2: 3 // Pork heals 1.5
Debug: Item<64> - paramInt2: 8 // Grilled pork heals 4.
Debug: Item<66> - paramInt2: 42 // Surprisingly, the Golden Apple restores 21 hearts. (Instead of 10 - the current maximum)
public ev a(ev paramev, cn paramcn, dm paramdm)
paramev - Stack related - Used to decrement stack count. (paramev.a)
paramcn - Not used; nor is it used by di generic item-class. Likely used elsewhere.
paramdm - Damage related. Determines how much health to restore.
Analysis / Findings:
Method b of paramdm restores health. Suggests that dm represents an entity - likely as it extends kh dynamic-object generic (Possibly main player. Method will only restore health to a living player i.e. >0 health). It will not restore above maximum. Also appears to do something related to maximum health. (Related to attributed 'j' and 'aW' in dm - warrants further investigation.)
Noteworthy is the fact that the parameter is signed and no validation is done on 'negative' healing. This means that damaging foods (Poisons) should work. This has been tested and confirmed.
Attributes
aT (Inherited) - Max Stack Size.
a - Hearts Healed by Item * 2.
dw.class - Crafting Class Methods public dw()
To Test: Instantiated at start-up
Analysis / Findings:
Makes a number of calls, passing itself as parameter - requires further investigation.
Following this is a list of calls to a (see below).
public ev avoid a(ev paramev, Object[] paramArrayOfObject)
paramev - New item "stack" to create (Constructor takes a block-generic or item-generic and a number (init. stack size).
paramArrayOfObject - Name-Value pair for recipe - organisation as follows (Untested)
Analysis / Findings:
The first index is the top row of the recipe. Second is (optionally) the middle row. Third is (optionally) the bottom row.
The next index is the first character used to represent the top row.
The next index is the item or block represented by that character.
First parameter: Make a new stack of minecart tracks (16 in size).
Second parameter: Recipe - lets examine in detail:
"X X", "X#X", "X X", ---> Let's re-arrange into a more familiar format:
X X
X#X
X X
Next, lets look at the parameters that follow in the object array:
Character.valueOf('X'), di.m, Character.valueOf('#'), di.B
So:
'X' maps to di.m
'#' maps to di.B
di is the generic item class.
Attribute m: public static di m = new di(9).a(23);
To shorten the analysis: The constructor for di takes an integer, which represents the decimal data value of the item - 256.
256 + 9 = 265 = Iron Ingot (http://www.minecraftwiki.net/wiki/Data_values)
So X is iron ingot.
Attribute B public static di B = new di(24).a(53).d();
256 + 24 = 280 = Stick
Substitute "Iron Ingot" for X and "Stick" for # and we end up with:
Which is indeed the recipe for iron ingot.
Further information:
The size of the "Recipe box" appears to be dynamic - this seems to indicate a somewhat easily extensible crafting area.
The "item" and "block" substitution function appears to function for arbitrary length. This means there should be no upper bound on the items that can be used.
Take the above with a grain of salt; need to test.
I was able to edit the id class can get it to compile and do a get/set coords from there. I'll post how once I'm done testing.
It's not exactly all I wanted and a pony but it is serviceable. I guess that's the way things are when you're hacking someone else's compiled, obfuscated code.
I use jd-gui to decompile, and netbeans to recompile. You can't try to decompile the whole jar, and you can't try to de-obfuscate things, because you're not modding the whole thing, just individual classes. I know little about the client, as the server is my realm of expertise. A few tips:
1) ez.class (currently) is a list of all item classes mapped to their data value (minus 256).
2) ff.class (currently) is a list of all block classes likewise mapped to their data value (actual value, not minus 256).
3) dy.class (currently) is the world, and you use dy.a(x,y,z) to get a block, and dy.d(x,y,z,v) to set one.
4) any entities use "L,M,N" instead of "X,Y,Z" for their coordinates.
Maybe I'm being too optimistic, but couldn't we start writing our own mod API? A library of interfaces that modders code against, and another assembly that maps the decompiled obfuscated objects to those interfaces that could be swapped according to the version being used. Then mods would be backwards and forwards compatible once someone writes the mapping for that specific version.
Don't think it would be hard to convince people to use the API once it's written - kicks the crap outta trying to remember that "dy.d(int, int, int, v)" is actually "world.setBlock(int, int, int, IBlockObject)" or whatever.
If you want to install a modified class into minecraft_server.jar and not corrupt it, the easiest way is to use the jar tools that come with whatever JDK you used to compile the class.
So let's say you have your minecraft_server.jar and your newly compiled id.class. Throw them in the same folder, navgate there with the terminal and issue this command:
jar uf minecraft_server.jar id.class
And that's it. Should take care of it. Works like a charm. Should work for mods other people have created. Read up on how to use jar if you want to do something more complex. THis should work for Windows too, by the way. Might be easier than fiddling with 7zip.
I've now got a custom command called "/myhome" which warps you to an arbitrary set of coordinates I typed into the source file.
Since it appears that each player gets his or her own instance of id.class and there is a variable that is the respective player's name, I'll have it read in a file at the end of the constructor, scan for the player's name, and set some variables for custom home coordinates that way. Then when the user issues a "/sethome" command, it can open up the file and overwrite these coordinates.
I'm certain I'm reinventing the wheel to some degree but that's ok. It's fun.
Tutorial: How to add custom spawn points to the SMP Server
Before we begin
This tutorial is designed to get people familiar with modding minecraft. If you just want to add custom spawn points, grab Hey0's mod, which does a lot more and has an installer and everything.
This tutorial assumes that you know how to work the command line for your respective platform and have moderate knowledge of Java programming. It should work on Windows, Mac OS X, and Linux.
Many thanks to all those who posted directions and gave advice in this thread.
Open up minecraft_server.jar in JD-GUI. In the lefthand column you will see all the packages and classes in the minecraft server. Everything in here is obfuscated so it's not going to be easy to find what you want. We are looking to set custom home points, so let's start by looking for the code that receives the "/home" command. Go to the Search feature and we'll look for string constants called "/home".
In the current version of the server, only one class pops up, id. If the version has changed, this might change. Let's take a look at id. Double click on it in the results. JD-GUI will jump to this decompiled class. In my version, se section around the string /home looks like this:
int m;
if (paramString.toLowerCase().equalsIgnoreCase("/home")) {
a.info(this.e.aq + " returned home");
m = this.d.e.d(this.d.e.n, this.d.e.p);
a(this.d.e.n + 0.5D, m + 1.5D, this.d.e.p + 0.5D, 0.0F, 0.0F);
As you can see, this is not going to be too easy to decypher. But that certainly is what we were looking for so we'll go to file, save source and save it to the same folder as minecraft_server.jar.
Open up id.java (or whatever yours is called) in a text editor and let's get to work modifying. First we'll need try to compile the class as-is and if needed, clean up any errors left behind by the decompilation process. Often these errors are simple, like not initializing a local variable or using the same local variable name inside and outside of a loop for different things.
Compiling the class is simple. Open up your terminal or command prompt and navigate to the folder where minecraft_server.jar and your java source are. Now run the javac command as follows:
javac id.java -cp minecraft_server.jar
NOTE: In Windows you might need to add javac, installed by the JDK, to your path variable. If it wasn't done for you by the installer, google it.
In my version, this compilation comes up with 4 errors:
$ javac id.java -cp minecraft_server.jar
id.java:70: cannot find symbol
symbol : variable d4
location: class id
/* 76 */ d4 = paramgf.d - paramgf.b;
^
id.java:71: cannot find symbol
symbol : variable d4
location: class id
/* 77 */ if ((d4 > 1.65D) || (d4 < 0.1D)) {
^
id.java:71: cannot find symbol
symbol : variable d4
location: class id
/* 77 */ if ((d4 > 1.65D) || (d4 < 0.1D)) {
^
id.java:73: cannot find symbol
symbol : variable d4
location: class id
/* 79 */ a.warning(this.e.aq + " had an illegal stance: " + d4);
^
Note: id.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
4 errors
Looking in the file, the error appears to be somewhere here:
The compiler said variable d4, cannot find symbol. Well, that's because glancing around the file, d4 was never defined before it was used! From context it appears to be a double. So let's go ahead and change line 76 to:
/* 76 */ double d4 = paramgf.d - paramgf.b;
Make the change in your text editor, recompile with javac and, at least on this version, everything should go just fine! You might have some more work to do. The more complex the file, the more likely there will be errors. There may be none, there may be 30. Work on as few classes as possible to minimize your effort. As far as I know, the MinecraftServer class is off limits. I've been unable to get it to compile successfully. Fortunately, not a lot is in there. If you want to add commands, the class we're working with now, the one with "/home" in it, is your best bet.
Injecting Your Recompiled Class
Eventually you'll have a file like id.class, a re-compiled version of the original. Let's insert this into the server jar, start it up and make sure everything is ok. To do this, I've found the easiest way is to use the command line JAR tool that comes with the JDK. So from the same terminal you just used to compile, try this:
jar uf minecraft_server.jar id.class
Obviously replacing id.class for the class you have modified. Again, Windows users may need to add their to their path. Fire up the server, connect to it and verify that everything is working with your recompiled module in place. If it is, great! Now you've got everything set up and error-free, you're ready to begin modding.
Alright, so you can compile the class, replace the stock classes with the ones you've recompiled, it's time to make some changes. My goal is to have in addition to the /home command two other commands: /myhome and /setmyhome. The set command to set a warp location and /myhome to send the user there. Let's take a look at home and figure out what we can do.
*/ int m;
/* 279 */ if (paramString.toLowerCase().equalsIgnoreCase("/home")) {
/* 280 */ a.info(this.e.aq + " returned home");
/* 281 */ m = this.d.e.d(this.d.e.n, this.d.e.p);
/* 282 */ a(this.d.e.n + 0.5D, m + 1.5D, this.d.e.p + 0.5D, 0.0F, 0.0F);
/* 283 */ } else if (paramString.toLowerCase().equalsIgnoreCase("/iron")) {
It looks like paramString is our command that was typed in, and it's being forced to all lower case, then compared against "/home". a.info() seems to be the function that prints out the message to the admin console. And since we know the message is always "Donkey_Kong returned home", we know that this.e.aq must be the user's name as a string.
The next two lines must somehow warp the player home. It looks like m = this.d.e.d(this.d.e.n, this.d.e.p) is some kind of calculation of where the spawn point really is and a() takes five numbers, that must be the player's destination! I assume the first three are X, Y, Z. I'm not certain what the next two are but they're floats and hard coded to zero zero. Most likely the player orientation. Where's they're looking.
Experimentation time! Let's make the /myhome command and not worry too much about exactly what it does. So we copy the home command and integrate into this if-statement. Send the player to (100,100,100)!
/* */ int m;
/* 279 */ if (paramString.toLowerCase().equalsIgnoreCase("/home")) {
/* 280 */ a.info(this.e.aq + " returned home");
/* 281 */ m = this.d.e.d(this.d.e.n, this.d.e.p);
/* 282 */ a(this.d.e.n + 0.5D, m + 1.5D, this.d.e.p + 0.5D, 0.0F, 0.0F);
} else if (paramString.toLowerCase().equalsIgnoreCase("/myhome")) {
a.info(this.e.aq + " went on an adventure");
a(100.0D, 100.0D, 100.0D, 0.0F, 0.0F);
/* 283 */ } else if (paramString.toLowerCase().equalsIgnoreCase("/iron")) {
Compile it and insert it into your server jar and test it out. In my world I blinked into the sky and fell crashing down on top of a building not far from my spawn point. When you're done, let's learn to control this better.
Variables and Intuition
Looking at the method a(d,d,d,f,f) should provide some insight into what is happening to teleport the player. Locate it. Mine looks like this:
Well, I don't know what j is and why it's always false, but apparently the numbers that come in simply set g, h, and i in this very class. If you think about it for a minute, that must mean that this class has one instantiation for each connected player. That's a pretty key understanding! This class is part of the representation of a player. If you remember back to the message of the player's name, "this.e.aq" was the string of a player's name. And there is a function here, this.e.a.b() which takes only coordinates, no player identifier. So this.e must also be an object that represents the player, one per player. But enough intuition, the take-away here is that there's a good chance that this.g, this.h, and this.i are the player's coordinates. Let's just hope that some other method somewhere keeps them up to date.
Twisting the Code
Let's move on and make /setmyhome. First we're going to need a semi-persistent way to store these coordinates. So up at the top of the file in the class declaration for id, let's add our own variables.
/* */ public class id extends ej
/* */ implements ef
/* */ {
/* 19 */ public static Logger a = Logger.getLogger("Minecraft");
/* */ public bb b;
/* 22 */ public boolean c = false;
/* */ private MinecraftServer d;
/* */ private ea e;
/* 25 */ private int f = 0;
/* */ private double g;
/* */ private double h;
/* */ private double i;
/* 50 */ private boolean j = true;
/* */
/* 135 */ private gp k = null;
// Here's the location we'll warp to. Don't execute a warp
// unless one has been set! Who knows where (0,0,0) is!
private double pos_x = 0;
private double pos_y = 0;
private double pos_z = 0;
private boolean warpset = false;
Now back down in in that /home if structure, we'll insert this:
/* 281 */ m = this.d.e.d(this.d.e.n, this.d.e.p);
/* 282 */ a(this.d.e.n + 0.5D, m + 1.5D, this.d.e.p + 0.5D, 0.0F, 0.0F);
// HACK BEGINS
} else if (paramString.toLowerCase().equalsIgnoreCase("/myhome") && this.warpset) {
a.info(this.e.aq + " returned to custom home");
a(this.x_pos, this.y_pos, this.z_pos, 0.0F, 0.0F);
} else if (paramString.toLowerCase().equalsIgnoreCase("/setmyhome")) {
a.info(this.e.aq + " set up a new home");
this.x_pos = this.g;
this.y_pos = this.h;
this.z_pos = this.i;
this.warpset = true;
// HACK ENDS
/* 283 */ } else if (paramString.toLowerCase().equalsIgnoreCase("/iron")) {
/* 284 */ if (MinecraftServer.b.containsKey(this.e.aq)) {
Pretty straightforward, eh? You can compile and test this but there's one obvious flaw: no persistence! Who knows when this object gets deleted or cleaned up? Probably when the user disconnects and certainly whenever the server is shut down. And as soon as that happens, posx,y,z get reset to zero and setwarp goes back to false. We'll need to figure out some way to give this hack some staying power.
To give this hack some staying power, we need to save the custom coordinates to a file. I whipped up these two functions which use java.util.Properties and java.io to save the custom coordinates to a file. I wasn't even sure what the file would look like but I knew that if I followed the documentation from Sun, I'd be alright. So here are the two functions which I inserted as methods of the id class.
public void getcustomhome(){
Properties coords = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream("custom_warps.conf");
try {
coords.load(in);
in.close();
if (coords.containsKey(this.e.aq+"_x"))
{
this.x_pos = Double.parseDouble(coords.getProperty(this.e.aq+"_x"));
this.y_pos = Double.parseDouble(coords.getProperty(this.e.aq+"_y"));
this.z_pos = Double.parseDouble(coords.getProperty(this.e.aq+"_z"));
this.warpset = true;
}
} catch (IOException e) {
a.info(this.e.aq + " failed to read custom warp file");
return;
}
} catch (FileNotFoundException e) {
a.info(this.e.aq + " failed to find custom warp file");
return;
}
}
public void setcustomhome(){
Properties coords = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream("custom_warps.conf");
try {
coords.load(in);
in.close();
} catch (IOException e) {
a.info(this.e.aq + " failed to read custom warp file");
}
} catch (FileNotFoundException e) {
a.info(this.e.aq + " cound not find custom warp file. Creating one...");
}
FileOutputStream out = null;
try {
out = new FileOutputStream("custom_warps.conf");
try {
coords.setProperty(this.e.aq+"_x", Double.toString(this.x_pos));
coords.setProperty(this.e.aq+"_y", Double.toString(this.y_pos));
coords.setProperty(this.e.aq+"_z", Double.toString(this.z_pos));
coords.store(out,"Custom Warp Points Hack");
out.close();
} catch (IOException e) {
a.info(this.e.aq + " failed to write custom warp file.");
}
} catch (FileNotFoundException e1) {
a.info(this.e.aq + " failed to write custom warp file. Permissions?");
}
}
I also needed to add the following imports since I used these functions:
import java.util.Properties; // Hack uses this to store warp points
import java.io.*;
The gist of these functions is that getcustomhome() looks in a file for coords for a player and if it finds them, it sets warpset, pos_x,y,z and then the user can warp right away. I placed a call to this function at the end of the constructor of id, taking care to note that this.e was set first, since I knew I'd be using it to find the player's name.
setcustomhome() modifies the file each time the /setmyhome file is called. So I just call that here:
} else if (paramString.toLowerCase().equalsIgnoreCase("/setmyhome")) {
a.info(this.e.aq + " set up a new home");
this.x_pos = this.g;
this.y_pos = this.h;
this.z_pos = this.i;
this.warpset = true;
setcustomhome();
// HACK ENDS
Of course there is a TON of error handling going on in those functions. I really really don't want to crash the server in the event that my hack fails for any reason at all, but other than that, it's extremely straightforward.
I hope this tutorial helped you get started modifying minecraft. I tried to keep things as general as I could in the explanation, even if I used specific variable names which will change. I also wanted something that anyone could use regardless of platform and IDE experience, so I made sure not to use an IDE.
Has anyone been taking inventory off all the identified classes and methods? I remember there was a modding wiki a few months back, but I kind of don't think it's around anymore.
Has anyone been taking inventory off all the identified classes and methods? I remember there was a modding wiki a few months back, but I kind of don't think it's around anymore.
Each time Notch issues an update, all the class names, methods, functions and variables change names. It's not really worth it, so I didn't really consider doing it.
Well, we've still got 3 weeks and change until the update, but I suppose it wouldn't make a whole lot of sense in the long run. I was thinking that if the relation between classes were mapped, they would be easier to find after the update, though that assumes most of the code hasn't changed significantly.
I like to work backwards from the text strings in the save format. I started from "Blocks" and found the terrain generator. Then, I looked at "TerrainPopulated" and found the second stage of the terrain generator. To poke at the renderer, I searched for a few OpenGL calls.
If you're familiar with Java, you probably know a few Java library calls that are good to search for.
Has anyone been taking inventory off all the identified classes and methods? I remember there was a modding wiki a few months back, but I kind of don't think it's around anymore.
Each time Notch issues an update, all the class names, methods, functions and variables change names. It's not really worth it, so I didn't really consider doing it.
I've been making an inventory of classes, methods, with accompanying analysis (See previous post). While it's certainly true that updates modify a significant portion of the naming convention for most updates it should be possible to create a program to try and 'match' method signatures and general structure between two versions; it's simple enough to do an eyeball difference; the trick is getting the knowledge representation in a concrete format and designing an appropriate fuzzy-matching algorithm.
Quick question, as I've been running into the same problems a lot of people have been with trying to recompile the .java source back into the compiled class files.
Can one of you wonderful devs that have this working do a quick video walk-through (from scratch) in eclipse/netbeans/commandline showing how to recompile the java back into a class file?
I'd assume start from the extraction of the minecraft.jar, decompile something like a.class using JD, show us what you're editing in the file to get it to work, then recompile using whichever IDE/commandline you prefer. That should help those of us that are having problems and we might be able to catch something simple we're forgetting,
This seems to be the only real step that's stopping a few people from messing around with mods and getting comfortable with the obfuscated source.
But I also wanted to use this as a way to build up a knowledge base of how to do it on my own.
Thank you. It solves the problem I had. I knew it was something like this but not being familiar with Netbeans, I didn't do it correctly .
I'll add theses informations to the first post.
I think it would not be against the forum and game rules to do such things.
If so, i'll keep a list at the top of the thread...
(Have to read forum and game agreement before just to be certain of that )
Some classes have already been mentionned in the first page ( Server classes and Client class one post later)
Assumed decompilation is done with Java Decompiler (JD).
oj.class - Generic superclass for Food
Methods
public oj(int paramInt1, int paramInt2)
paramInt1 - Passed to di superclass. Appears to be the Item ID.
paramInt2 - Sets attribute 'a'.
Unsure of purpose; Does not appear to be stack-size. Requires further testing.Hearts Restored * 2.Analysis / Findings:
Debugging has revealed this class is instantiated at login. Initial analysis appears to indicate paramInt2 determines how much health the item heals:
public ev a(ev paramev, cn paramcn, dm paramdm)
paramev - Stack related - Used to decrement stack count. (paramev.a)
paramcn - Not used; nor is it used by di generic item-class. Likely used elsewhere.
paramdm - Damage related. Determines how much health to restore.
Analysis / Findings:
Method b of paramdm restores health. Suggests that dm represents an entity - likely as it extends kh dynamic-object generic (Possibly main player. Method will only restore health to a living player i.e. >0 health). It will not restore above maximum. Also appears to do something related to maximum health. (Related to attributed 'j' and 'aW' in dm - warrants further investigation.)
Noteworthy is the fact that the parameter is signed and no validation is done on 'negative' healing. This means that damaging foods (Poisons) should work. This has been tested and confirmed.
Attributes
aT (Inherited) - Max Stack Size.
a - Hearts Healed by Item * 2.
dw.class - Crafting Class
Methods
public dw()
To Test: Instantiated at start-up
Analysis / Findings:
Makes a number of calls, passing itself as parameter - requires further investigation.
Following this is a list of calls to a (see below).
public ev avoid a(ev paramev, Object[] paramArrayOfObject)
paramev - New item "stack" to create (Constructor takes a block-generic or item-generic and a number (init. stack size).
paramArrayOfObject - Name-Value pair for recipe - organisation as follows (Untested)
Analysis / Findings:
The first index is the top row of the recipe. Second is (optionally) the middle row. Third is (optionally) the bottom row.
The next index is the first character used to represent the top row.
The next index is the item or block represented by that character.
Example:
a(new ev(ly.aH, 16), new Object[] { "X X", "X#X", "X X", Character.valueOf('X'), di.m, Character.valueOf('#'), di.B });
First parameter: Make a new stack of minecart tracks (16 in size).
Second parameter: Recipe - lets examine in detail:
"X X", "X#X", "X X", ---> Let's re-arrange into a more familiar format:
X X
X#X
X X
Next, lets look at the parameters that follow in the object array:
Character.valueOf('X'), di.m, Character.valueOf('#'), di.B
So:
'X' maps to di.m
'#' maps to di.B
di is the generic item class.
Attribute m: public static di m = new di(9).a(23);
To shorten the analysis: The constructor for di takes an integer, which represents the decimal data value of the item - 256.
256 + 9 = 265 = Iron Ingot (http://www.minecraftwiki.net/wiki/Data_values)
So X is iron ingot.
Attribute B public static di B = new di(24).a(53).d();
256 + 24 = 280 = Stick
Substitute "Iron Ingot" for X and "Stick" for # and we end up with:
Which is indeed the recipe for iron ingot.
Further information:
The size of the "Recipe box" appears to be dynamic - this seems to indicate a somewhat easily extensible crafting area.
The "item" and "block" substitution function appears to function for arbitrary length. This means there should be no upper bound on the items that can be used.
Take the above with a grain of salt; need to test.
It's not exactly all I wanted and a pony but it is serviceable. I guess that's the way things are when you're hacking someone else's compiled, obfuscated code.
Maybe I'm being too optimistic, but couldn't we start writing our own mod API? A library of interfaces that modders code against, and another assembly that maps the decompiled obfuscated objects to those interfaces that could be swapped according to the version being used. Then mods would be backwards and forwards compatible once someone writes the mapping for that specific version.
Don't think it would be hard to convince people to use the API once it's written - kicks the crap outta trying to remember that "dy.d(int, int, int, v)" is actually "world.setBlock(int, int, int, IBlockObject)" or whatever.
If you want to install a modified class into minecraft_server.jar and not corrupt it, the easiest way is to use the jar tools that come with whatever JDK you used to compile the class.
So let's say you have your minecraft_server.jar and your newly compiled id.class. Throw them in the same folder, navgate there with the terminal and issue this command:
jar uf minecraft_server.jar id.class
And that's it. Should take care of it. Works like a charm. Should work for mods other people have created. Read up on how to use jar if you want to do something more complex. THis should work for Windows too, by the way. Might be easier than fiddling with 7zip.
I've now got a custom command called "/myhome" which warps you to an arbitrary set of coordinates I typed into the source file.
Since it appears that each player gets his or her own instance of id.class and there is a variable that is the respective player's name, I'll have it read in a file at the end of the constructor, scan for the player's name, and set some variables for custom home coordinates that way. Then when the user issues a "/sethome" command, it can open up the file and overwrite these coordinates.
I'm certain I'm reinventing the wheel to some degree but that's ok. It's fun.
Before we begin
This tutorial is designed to get people familiar with modding minecraft. If you just want to add custom spawn points, grab Hey0's mod, which does a lot more and has an installer and everything.
This tutorial assumes that you know how to work the command line for your respective platform and have moderate knowledge of Java programming. It should work on Windows, Mac OS X, and Linux.
Many thanks to all those who posted directions and gave advice in this thread.
The Right Tools
Before you begin, you will need three things: a clean copy of the JAR version of SMP server, JD-GUI, the java decompiler with an interface, and the Java Development Kit (JDK).
De-compile, Repair and Re-compile
Open up minecraft_server.jar in JD-GUI. In the lefthand column you will see all the packages and classes in the minecraft server. Everything in here is obfuscated so it's not going to be easy to find what you want. We are looking to set custom home points, so let's start by looking for the code that receives the "/home" command. Go to the Search feature and we'll look for string constants called "/home".
In the current version of the server, only one class pops up, id. If the version has changed, this might change. Let's take a look at id. Double click on it in the results. JD-GUI will jump to this decompiled class. In my version, se section around the string /home looks like this:
As you can see, this is not going to be too easy to decypher. But that certainly is what we were looking for so we'll go to file, save source and save it to the same folder as minecraft_server.jar.
Open up id.java (or whatever yours is called) in a text editor and let's get to work modifying. First we'll need try to compile the class as-is and if needed, clean up any errors left behind by the decompilation process. Often these errors are simple, like not initializing a local variable or using the same local variable name inside and outside of a loop for different things.
Compiling the class is simple. Open up your terminal or command prompt and navigate to the folder where minecraft_server.jar and your java source are. Now run the javac command as follows:
javac id.java -cp minecraft_server.jar
NOTE: In Windows you might need to add javac, installed by the JDK, to your path variable. If it wasn't done for you by the installer, google it.
In my version, this compilation comes up with 4 errors:
Looking in the file, the error appears to be somewhere here:
The compiler said variable d4, cannot find symbol. Well, that's because glancing around the file, d4 was never defined before it was used! From context it appears to be a double. So let's go ahead and change line 76 to:
/* 76 */ double d4 = paramgf.d - paramgf.b;
Make the change in your text editor, recompile with javac and, at least on this version, everything should go just fine! You might have some more work to do. The more complex the file, the more likely there will be errors. There may be none, there may be 30. Work on as few classes as possible to minimize your effort. As far as I know, the MinecraftServer class is off limits. I've been unable to get it to compile successfully. Fortunately, not a lot is in there. If you want to add commands, the class we're working with now, the one with "/home" in it, is your best bet.
Injecting Your Recompiled Class
Eventually you'll have a file like id.class, a re-compiled version of the original. Let's insert this into the server jar, start it up and make sure everything is ok. To do this, I've found the easiest way is to use the command line JAR tool that comes with the JDK. So from the same terminal you just used to compile, try this:
jar uf minecraft_server.jar id.class
Obviously replacing id.class for the class you have modified. Again, Windows users may need to add their to their path. Fire up the server, connect to it and verify that everything is working with your recompiled module in place. If it is, great! Now you've got everything set up and error-free, you're ready to begin modding.
Alright, so you can compile the class, replace the stock classes with the ones you've recompiled, it's time to make some changes. My goal is to have in addition to the /home command two other commands: /myhome and /setmyhome. The set command to set a warp location and /myhome to send the user there. Let's take a look at home and figure out what we can do.
It looks like paramString is our command that was typed in, and it's being forced to all lower case, then compared against "/home". a.info() seems to be the function that prints out the message to the admin console. And since we know the message is always "Donkey_Kong returned home", we know that this.e.aq must be the user's name as a string.
The next two lines must somehow warp the player home. It looks like m = this.d.e.d(this.d.e.n, this.d.e.p) is some kind of calculation of where the spawn point really is and a() takes five numbers, that must be the player's destination! I assume the first three are X, Y, Z. I'm not certain what the next two are but they're floats and hard coded to zero zero. Most likely the player orientation. Where's they're looking.
Experimentation time! Let's make the /myhome command and not worry too much about exactly what it does. So we copy the home command and integrate into this if-statement. Send the player to (100,100,100)!
Compile it and insert it into your server jar and test it out. In my world I blinked into the sky and fell crashing down on top of a building not far from my spawn point. When you're done, let's learn to control this better.
Variables and Intuition
Looking at the method a(d,d,d,f,f) should provide some insight into what is happening to teleport the player. Locate it. Mine looks like this:
Well, I don't know what j is and why it's always false, but apparently the numbers that come in simply set g, h, and i in this very class. If you think about it for a minute, that must mean that this class has one instantiation for each connected player. That's a pretty key understanding! This class is part of the representation of a player. If you remember back to the message of the player's name, "this.e.aq" was the string of a player's name. And there is a function here, this.e.a.b() which takes only coordinates, no player identifier. So this.e must also be an object that represents the player, one per player. But enough intuition, the take-away here is that there's a good chance that this.g, this.h, and this.i are the player's coordinates. Let's just hope that some other method somewhere keeps them up to date.
Twisting the Code
Let's move on and make /setmyhome. First we're going to need a semi-persistent way to store these coordinates. So up at the top of the file in the class declaration for id, let's add our own variables.
Now back down in in that /home if structure, we'll insert this:
Pretty straightforward, eh? You can compile and test this but there's one obvious flaw: no persistence! Who knows when this object gets deleted or cleaned up? Probably when the user disconnects and certainly whenever the server is shut down. And as soon as that happens, posx,y,z get reset to zero and setwarp goes back to false. We'll need to figure out some way to give this hack some staying power.
To give this hack some staying power, we need to save the custom coordinates to a file. I whipped up these two functions which use java.util.Properties and java.io to save the custom coordinates to a file. I wasn't even sure what the file would look like but I knew that if I followed the documentation from Sun, I'd be alright. So here are the two functions which I inserted as methods of the id class.
I also needed to add the following imports since I used these functions:
The gist of these functions is that getcustomhome() looks in a file for coords for a player and if it finds them, it sets warpset, pos_x,y,z and then the user can warp right away. I placed a call to this function at the end of the constructor of id, taking care to note that this.e was set first, since I knew I'd be using it to find the player's name.
setcustomhome() modifies the file each time the /setmyhome file is called. So I just call that here:
Of course there is a TON of error handling going on in those functions. I really really don't want to crash the server in the event that my hack fails for any reason at all, but other than that, it's extremely straightforward.
I hope this tutorial helped you get started modifying minecraft. I tried to keep things as general as I could in the explanation, even if I used specific variable names which will change. I also wanted something that anyone could use regardless of platform and IDE experience, so I made sure not to use an IDE.
Now get hacking!
Each time Notch issues an update, all the class names, methods, functions and variables change names. It's not really worth it, so I didn't really consider doing it.
If you're familiar with Java, you probably know a few Java library calls that are good to search for.
"We will absolutely not keep in mind what external mapeditors will have to do to read data from the disk, that makes no sense whatsoever." - Grum
I've been making an inventory of classes, methods, with accompanying analysis (See previous post). While it's certainly true that updates modify a significant portion of the naming convention for most updates it should be possible to create a program to try and 'match' method signatures and general structure between two versions; it's simple enough to do an eyeball difference; the trick is getting the knowledge representation in a concrete format and designing an appropriate fuzzy-matching algorithm.
Can one of you wonderful devs that have this working do a quick video walk-through (from scratch) in eclipse/netbeans/commandline showing how to recompile the java back into a class file?
I'd assume start from the extraction of the minecraft.jar, decompile something like a.class using JD, show us what you're editing in the file to get it to work, then recompile using whichever IDE/commandline you prefer. That should help those of us that are having problems and we might be able to catch something simple we're forgetting,
This seems to be the only real step that's stopping a few people from messing around with mods and getting comfortable with the obfuscated source.