Assembler

The assembler lets you edit the bytecode of Java classes in a low level format. It should be used whenever possible as opposed to recompiling decompiled code. Using the assembler is comparable to a surgeon using a scalpel, verses recompiling which is comparable to them using a sledgehammer for the same operation.

Method Assembler

A method open in the assembler, showing various method attributes and instructions.

How do I open the assembler?

The assembler for any class, field, or method can be accessed by right clicking on the name of the class, field, or method and selecting "Edit > Edit in assembler". If for some reason right clicking does not work (which can occur when the context parser cannot understand obfuscated code) you can also right click on items in the "Fields & Methods" tab shown on the right hand side of any open class.

Accessing the Assembler

A short animation showing how to open the assembler.

Examples of where to click:

//   "Hello" will open the class assembler
//     |
//     V
class Hello {
    //    "message" will open the field assembler
    //       |
    //       V
    String message = "hi";

    // "foo" will open the method assembler
    //   |
    //   V
    void foo() {
        System.out.println(this.message);
    }
}

Additionally, you can right click on the class in the workspace explorer (which is where the tree of classes are on the left) to open the class level assembler.

I don't know much about bytecode, what now?

Ideally you can learn just enough to get by. Here are some relevant pages covering the basics of Java bytecode:

After you read over these pages the additional tools and features offered by the assembler should be able to carry you the rest of the way to making your desired changes.

Assembler Features

Java to Bytecode

You can write snippets of Java code and the Java to Bytecode tool will generate the equivalent bytecode. It uses the standard javac compiler behind the scenes, so whatever version of Java you run Recaf with dictates what features you have access to. But this means as long as you can fit your snippet into a continous block of code (no separate method definitions) it'll be supported here. Though, there are other shortcuts included like the ability to add import statements to the top.

The editor is tied to whatever context the assembler is open with. For instance, if you open the assembler on a method that returns String (or any Object type) it will expect that your snippet of Java code also eventually returns a String (You can ignore it and keep the default return null if desired).

Another benefit of this context-sensitive compiler is that you can access information in that context. You will always have access to the current class's fields and methods. But when you open a method in the assembler you will also have access to the parameters and defined local variables of the method.

For instance, consider this method:

double combine(double base, double power, double extra) {
	double tmp = Math.pow(base, power);
    return tmp + extra;
}

In this context you not only have access to the parameters base, power, and extra, but you also can access tmp in the Java to Bytecode tool. This means you could write something like return tmp - extra.

Snippets

If you find yourself regularly using the Java to Bytecode feature for the same kind of operation you may want to keep a copy of the results as a snippet. Snippets are just segments of code that you find useful for copy-pasting later.

Recaf comes with a few example snippets. They have comments in them outlining what the original source code equivalent is and then the bytecode that corresponds to that source:

  • for (int i = 0; i < 10; i++) someMethod();
  • while (i >= 0) { someMethod(); i--; }
  • if (b) whenTrue(); else whenFalse();
  • System.out.println("Hello");
  • System.out.printf("hello %s\n", name);

When you create your own snippet you are prompted to give it a name and description. Afterwards you can type out our desired code snippet in the editor and then press the save button to keep it.

Analysis of stack/locals

While it is encouraged to read the JVM Bytecode Instructions and execution pages, mentally keeping track of how things move around on the stack and are stored in local variables can be tedious. The Analysis will do all of this for you. Contents of easily computed values (primitives and Strings) will also show their current values based on where your caret/text position is at within the method assembler.

Control flow lines

Some instructions change the control flow of program exection. Remembering what label a jump goes to and scrolling to it (or using Control + F to find it) is tedious. Instead, when you click on any label or any instruction that potentially manipulates control flow you will see where those instructions will lead to. To disambiguate which jumps go where, any time there are multiple control flow lines next to one another they will use a distinct color. Ihe color selection is based on a hue rotation, so something like a large switch statement will generate a rainbow.

Control flow lines in the assembler

A series of lines between instructions and referenced labels.

Tab completion

The names of instructions and field/method references can be tab completed.

Tab complete in the assembler

A short animation showing how tab completion can be used to make writing bytecode faster.