Metaphysical Developer

Conjcraft: A Minecraft Mod implemented in Clojure

Posted in hacking, Languages, Minecraft by Daniel Ribeiro on April 20, 2012

When you don’t create things, you become defined by your tastes rather than ability. Your tastes only narrow & exclude people. So create.

— why the lucky stiff

TL;DR:  Source here, and here a video of the mod in action:

Conjcraft

Conjcraft is a simple and extensible Mod for Minecraft written in Clojure (and some Java). Besides introducing two new blocks (Clojure and Github, which is hosting the source here), it brings an extremely simple and small DSL for writing Minecraft recipes.

The recipe DSL cleans up on on Minecrafts original one (which is alredy terse for a Java DSL). Compare these simple ones:

addRecipe(new ItemStack(Block.rail, 16), new Object[]
                {
                    "X X", "X#X", "X X", 'X', Item.ingotIron, '#', Item.stick
                });
(recipe-dsl {\X :ingotIron \# :stick}
  "X X
   X#X
   X X" 'rail 16)

Small explanation: the Clojure version is essentially the ascii art of this recipe:

Disclaimer: I’ll not try to teach Clojure here (besides saying it is a Lisp). If you need more info, there are great resources on the web.

This gain in expressiveness (which is come from the fact that Clojure is extremely more expressive than Java) is compounded in multiple recipes, specially after defining a consistent character to block/item mapping:

(def char-block (create-input-char-binding
                  '(
                     d dirt
                     o cobblestone
                     g github
                     c clojure
                     r redstone
                     )))

Many recipes can use them:

(defn recipes []
  (recipe-dsl char-block
     "d
      d" 'github

     "o
      o" 'clojure

     "c
      c
      c" 'swordGold

     "c c
      c c" 'bootsGold

     "cgc
      cgc" 'bootsDiamond

     "ccc
      c c
      c c" 'legsGold

     "ccc
      cgc
      cgc" 'legsDiamond
    ))

And finally, all of this is encoded in plain text Clojure files, stored in the conjcraft directory inside  user.home (which on Linux and Mac OS it is usually the user’s home directory, aka ~).

This way Conjcraft is very extensible, as it allows the users to add blocks and recipes, without requiring Eclipse or MCP, or to recompile and obfuscate the de-obfuscated Java code.

Such simplicity, though, did not come easily…

Origins

One of the things that has always amazed me about Minecraft is how simple its concept is. I believe this simplicity is actually paramount to its success: by giving you very solid and small building blocks (no pun intended), the game steps away and let the user create its own goals and be shine on its own.

This simplicity also lets other developers step in and create a huge variety of amazing mods (out of which, one my personal favorites is the Aether mod, for being a very ambitious project, and showing how much great content you can create on top of such a simple and powerful platform).

“Simplicity Ain’t Easy”: Stuart Halloway masterfully made this argument, exploring what simple is (one of the key points being that simple is “not compound”), its importance, and how Clojure is a simple language, which actually makes it very powerful. Inspired on the simplicity and power of both Clojure and Minecraft (and continuing my healthy(?) obsession with Minecraft and Clojure) it seemed only natural for me to set to create a simple mod on top of both platforms (natural because both of them run on top of JVM).

Modding Minecraft with Java is quite straightforward with the help of Minecraft Coder Pack (aka MCP) and ModLoader. Calling clojure from Java is also very straightforward, to the point that you basically need a Java class like this:

public class mod_Conjcraft extends BaseMod {
    public void load() {
        try {
            File file = new File(new File(System.getProperty("user.home"), "conjcraft"), "conjcraft_main.clj");
            System.out.println("Loading clojure mod files from " + file.getAbsolutePath());
            clojure.lang.Compiler.loadFile(file.getAbsolutePath());
            clojure.lang.RT.var("conjcraft", "call").invoke();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
}

And then I was able to create a very small function, in 5 lines of Clojure, to add a recipe that would take one block off dirt and output 7 blocks of dirt:

(ns conjcraft)
(import '(net.minecraft.src Block ModLoader ItemStack))
(defn call []
  (let [dirt Block/dirt]
    (ModLoader/addRecipe (ItemStack. dirt 7) (to-array ["#" \# dirt]))))

This actually works pretty well when using Eclipse, or the recompile.sh script that comes with MCP. The fun really began when I started preparing to release it…

The 1st rule of the Obfuscator Club is:

You can’t defeat the obfuscator. This is actually really important. Minecraft is obfuscated in its original distribution, which makes a lot of sense for a proprietary and commercial game. MCP tools de-obfuscate the original java code from its original form, giving methods and classes names very straightforward and sensible names.

The problem is that, in general, gamers will need your mod in the obfuscate code, as they game expects classes to use the obfuscated names. Therefore you absolutely must obfuscate your mod.

The 2nd rule of the Obfuscator Club is:

You can’t defeat the obfuscator.

Clojure does have the capability of generating .class files with its Ahead of Time (AOT) compiler.  Since the obfuscator does not operate on java source code, but on .class files, this could have helped. But it doesn’t. Other languages that run on JVM like Scala (which compiles to pretty Java-like bytecode) and Mirah (which can even compile to Java source code) can actually get around the obfuscator this way, as long as you don’t use features that require reflection.

To understand why it doesn’t work with Clojure, let me show you what this simple AOT example:

(ns core
  (:gen-class :main true))

(defn -main []
  (println "Hello World!"))

With some help of JD-GUI we can see the equivalent Java code of the generated class files, in particular:

public class core
{
  private static final Var main__var = Var.internPrivate("core", "-main");
  private static final Var equals__var = Var.internPrivate("core", "-equals");
  private static final Var toString__var = Var.internPrivate("core", "-toString");
  private static final Var hashCode__var = Var.internPrivate("core", "-hashCode");
  private static final Var clone__var = Var.internPrivate("core", "-clone");

These seemly innocuous lines actually break in runtime. This happens because the obfuscator has another very important property: it puts everything on top level namespace (no packages). Note that the package “core” is written as a literal string, which the obfuscator will not touch. And currently there is no way to use AOT with empty namespaces

You could change the Clojure compiler, or use tools to manipulate the byte code on the class files, but there is actually a much simpler solution:

Breaking the rules: Defeating the Obfuscator

Clojure is famous for supporting one of the most powerful types of metaprogramming: template macros. I have not exploited it on the project because macros can be very hard to understand (think of them as functions that take code in its raw Abstract Syntax Tree form, and output another raw Abstract Syntax Tree), and I wanted to keep the project very accessible.

The point is that I used Clojure to generate Java source code, on compile time (the type of metaprogramming you always have the option to use, no matter the platform or base language you are based on).

This is done by the create_constants.clj script, which actually imports the de-obfuscated code and generates a Java file mapping all block, item and material names to their actual objects (the result cannot be published without breaking both Minecraft and MCP licenses, but reading the code you can get an idea of what the result looks like).

Using the property highlighted before, that the literal strings will not be obfuscated, and knowing that the obfuscator will not obfuscate the attribute names of classes you create (only make stripe their package), this static maps are available to be used directly by interpreted Clojure code.

The final element of defeating the obfuscator is the ExtendableBlock class. It essentially takes Clojure functions (clojure.lang.IFn interface), and delegate methods to them (some methods have to be re-exposed even when public, as the original public method names will be obfuscated).

Conclusions

Modding Minecraft is extremely fun, and it gets a lot more enjoying when doing it in languages that that are fun to use. I’ve used Clojure here, but there are many other languages that could have been used. So have fun, and create.

Thanks

Thanks Notch for making Minecraft and supporting the modding community. Thanks for all the presenters at ClojureWest for inspiring me to bring Clojure to new places. Thanks Robert for making one of the best Minecraft modding tutorials out there. And finally thanks to all the creators of MCP and ModLoader for making modding a simpler and pleasant experience.

Tagged with: , , ,

4 Responses

Subscribe to comments with RSS.

  1. James Reeves said, on April 22, 2012 at 9:32 am

    This is a really interesting project! But why is the repository laid out so strangely? I can understand the need for some shell scripts, but I don’t understand why you decided to go without Leiningen altogether.

    • Daniel Ribeiro said, on April 22, 2012 at 4:10 pm

      Hi James. Thanks for your interest. The project was envisioned to be easy to be picked up by anyone, no matter how high skilled in programming, and in particular, Clojure or Java.

      This way I tried to have as few moving parts as possible, and requiring as few dependencies as possible. Yes, it feels less Clojure-like, but I believe it makes the project more accessible.

      Hope that makes sense.

      • Hannes said, on February 10, 2013 at 12:52 pm

        I’m not sure I agree that you make the project more accessible by essentially alienating Clojure devs.

  2. Taufiq said, on June 22, 2012 at 7:03 am

    Very nice! With this and CmdrDats’s clj-minecraft, we now have both client and server mods covered :)


Leave a comment