feat: partially rewrote chapter one

This commit is contained in:
zaaarf 2023-09-08 12:47:00 +02:00
parent b6e7c36e12
commit 6325f4e791
No known key found for this signature in database
GPG key ID: 6445A5CD15E5B40C
3 changed files with 23 additions and 19 deletions

View file

@ -1,4 +1,10 @@
# An introduction to ASM patching # An introduction to ASM patching
ASM patching means, in short, to modify the "ASM" - that is, "assembly" - of an application at runtime. Within Java, this refers to the bytecode, which is the lower-level language your Java code gets compiled to. Due to the radical nature of modifying the game, before writing a patch one should always wonder whether patching is the correct approach. That is not to say that there is never a need for patching: you should just know that many problems can be solved by other means. "ASM patching" means, in short, to modify the "ASM" - that is, "[assembly](https://en.wikipedia.org/wiki/Assembly_language)" - of an application at runtime. In context, this refers to [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode), which is the instruction set of the [JVM](https://en.wikipedia.org/wiki/Java_virtual_machine). Java, Kotlin, Scala, Groovy and any other language targeting the JVM can, once compiled, be seen as bytecode.
Though reviled by many, ASM patching remains one of the most powerful tools at your disposal when modding a game. Like every tool, ASM patching is not evil in itself, rather it depends on how one uses it; when used correctly, it can solve just about any problem elegantly and with the lowest possible footprint; when done incorrectly, it can wreak havoc on the entire environment, causing inexplicable crashes and odd incompatibilities. Since you are modifying how the application works *and have no guarantees of being the only one doing so*, caution is paramount when working with ASM patches. Ask yourself: do I really need a patch to achieve this? Can I not go around it? Does doing so imply a performance loss? How big of a performance loss? Is it bearable?
In short, ASM patching should always be the very last resort. That is not to say that patching is useless: there are many problems that can only be effectively solved by modifying the bytecode of the target application; though, you should keep in mind that *most* problems can be effectively solved by other means.
Though reviled by many, ASM patching remains one of the most powerful tools in the Java modder's arsenal. Like every tool, ASM patching is not evil in itself. When used correctly, it can solve just about any problem elegantly with a minuscule footprint. When done incorrectly, it can wreak havoc on the entire environment, causing inexplicable crashes and pulling the rug from underneath everyone else wishing to modify the program just like you.
This latter issue has led most of the new generation of modders to reject ASM patching altogether, in favour of higher-level solutions, ditching the complexities of bytecode in favour of the checked safety of plain Java. In Minecraft's case, one such solution is [Mixin](https://github.com/SpongePowered/Mixin/).

View file

@ -1,12 +1,10 @@
# Why Lillero? # Why Lillero?
As you may have gleamed from the previous chapter, I am not a fan of Mixin. As you may have gleamed from the previous chapter, I am not a fan of Mixin. I respect its engineering, which is very clever, and acknowledge the problems it attempts to solve. My problem with it is that most of those problems are symptoms of a bigger one that Mixin fails to acknowledge.
I respect its engineering, which is admittedly clever, and acknowledge the problems it attempts to solve, but I reject their solutions.
[Lillero](https://github.com/zaaarf/lillero) was my alternative answer, which I wrote with a clear goal in mind: it should allow to do everything, while keeping it as comfortable as it can get this close to bare metal. Lillero is lightweight and flexible, but also easy to write when used to its full potential. Why do people fail at making patches? The answer is lack of checks mixed with general incompetence. Mixin thus set out to make it easy. My belief, though, is that the underlying issue is a general lack of readily available information on ASM patching. The Minecraft Forge forums soon banned discussion of the topic altogether, in a misguided attempt to discourage it. Should we be surprised that people are doing it wrong, if you can't talk about the topic in one of the biggest communities that may be interested in it?
The idea was to provide a simple interface, with methods to provide the metadata and one method where the implementer would get to perform his modifications. [Lillero](https://github.com/zaaarf/lillero) was my alternative answer to those problems. I wrote Lillero with a clear goal in mind: it should allow you to do everything, while keeping it as comfortable as it can get this close to bare metal. When used to its full potential, Lillero is lightweight and flexible, but also easy to write. Coupled with this book, it should empower anyone to write good patches following the best possible practices.
As we'll see, this resulted in quite a bit of boilerplate, which prompted the creation of the [Lillero-processor](https://github.com/zaaarf/lillero-processor/) to generate all of it.
*Generate* is the keyword here: repetitive tasks aren't removed or abstracted out, they are just made to write by the machine. One can open the generated files and easily see what each annotation does. By design, Lillero's inner workings should be clear and easy to follow for anyone working with it. Should one want to dig deeper, they'll find that all of Lillero's code is fully documented, with a Javadoc for every last method and field. At the heart of Lillero lies a Java interface, which any patch should implement: it will contain various methods, and any metadata that may be needed by the loader. As we'll see, you won't have to write most of this boilerplate by hand: the [Lillero-processor](https://github.com/zaaarf/lillero-processor/) will take care of generating it.
The Minecraft Forge Forums and many others would agree that this knowledge should be left buried in the sands of time where it is. I disagree: it's in the same spirit that I wrote this book. This should serve as a guide not to Lillero alone, but to the whole world of ASM patching. *Generating* is the keyword here: repetitive tasks aren't abstracted out, they are just made to write by the machine. One can open the generated files and easily see what each annotation does. By design, Lillero's inner workings should be clear and easy to follow for anyone wishing to learn. Should one want to dig deeper, they'll find that all code in the Lillero project is heavily documented, with a Javadoc for every last method and field, so that everything is perfectly clear to anyone wishing to learn from it.

View file

@ -1,14 +1,14 @@
# Why (not) Mixin? # Why (not) Mixin?
[Mixin](https://github.com/SpongePowered/Mixin/) is a bytecode manipulation framework that has become very popular in recent years. [Mixin](https://github.com/SpongePowered/Mixin/) is a bytecode manipulation framework that has become very popular in recent years. Though it also relies on the [ASM library](https://asm.ow2.io/), Mixin is not an "ASM patching" framework in the true meaning of the word. Self-described as a "bytecode-weaving" framework, it allows the user to manipulate the bytecode without having to manually write a single instruction.
Though it also relies on the [ASM library](https://asm.ow2.io/), Mixin is not an "ASM patching" framework in the true meaning of the word, though it has similar goals.
Self-described as a "bytecode-weaving" framework, it allows the user wishing to modify Java code to work at a considerably higher level.
The user of Mixin will be writing familiar Java, rather than mysterious bytecode instructions, and you'll use a system of annotations to determine where to write. The user of Mixin will be writing in Java (or any other JVM language), rather than raw bytecode instructions, using annotations to provide any metadata (such as location) your bytecode might need. Working with Mixin is undeniably easier: you're trading the surgical precision of ASM patching for safety and comfort. Mixin tries to provide ways to achieve most things patching can do: as a result, it has become huge - some would say bloated - and in spite of that its replacements are clunky and impractical due to the high amount of abstraction needed. Suppose, for example, that you wish to modify the conditions of an `if()` statement in some way: with raw patching, since `if`s are compiled down to conditional jump instructions, this is a trivial task, possibly one of the easiest you can face; with Mixin, you'll likely be duplicating and overwriting half the method: all the fancy crutches Mixin has given you now are just getting in your way.
You will have a far easier life working with Mixin, but you won't have the surgical precision that is the main strength of ASM patching.
Another advatage of working at a lower level is that you can do just about anything: Mixin is quite big, and the reason is that by hiding everything behind a simplified façade they now have to re-invent just about any operation that normal ASM patching allows you to do.
An unfortunately widespread myth is that Mixin "allows for greater compatibility" with other mods that work to modify the same part of the code. This is not true. A widespread myth is that Mixin "allows for greater compatibility" with other mods that work to modify the same part of the code. This is is a half-truth at best. Poorly written Mixins can break compatibility as much as any bad ASM patch; conversely, properly made Mixins will work just as well as properly written ASM patches.
Poorly written Mixins can break compatibility as much as any badly ASM patch; conversely, properly made Mixins will work just as well as properly written ASM patches.
The one true upside Mixin has is that it's stricter: it performs a number of checks to ensure the validity of what you wrote, a safety net that simply does not exist in raw ASM. This, however, has nothing to do with compatibility.
Mixin is also unfortunately huge; while most Minecraft mod loaders now bundle it (which is a questionable design choice, but whatever), this issue still exists when patching outside of those. In many cases, I've seen more than a program bundle a Mixin binary that was a dozen times bigger than itself. The main reason people say this is that the worst Mixin (one that `@Overwrite`s methods when it really isn't needed) is better than the worst ASM patch (one that injects its bytecode in the wrong spot): the former will simply erase any changes made by others, while the latter will crash your program in the best case, and cause weird behaviour in the worst. What I just said is an undeniable truth; it's also true that the best ASM patch is, depending on the task, equal to or better than the best Mixin, due to its superior precision and overall lower impact on the resulting code. Now, knowing this, ask yourself: are you aiming to write the best, or the worst?
The one upside Mixin *truly* has is that it's stricter: it performs a number of checks to ensure the validity of what you wrote, and since you're writing plain Java (or whatever other language), the compiler will also check the validity of your code. You have no such safety net in raw ASM.
Finally, as I mentioned, Mixin is a rather big library; while most Minecraft mod loaders nowadays bundle it (which is a questionable design choice, but that's a topic for another time), this is not the case in other environments. In many cases, I've seen Mixin binaries bigger than the programs they were supposed to be backing.
Ultimately, whether to use Mixin or ASM patching is a matter of personal preference. Lots of great programmers choose not to bother with the complexities of bytecode and instead entrust that part to Mixin, and lots of incompetent programmers try and fail to do it manually, creating the botched patches that sparked this whole debate. Unfortunately, the latter category has given a terrible reputation to ASM patching. The purpose of this chapter is to disprove such myths, and show that ASM patching can be an effective alternative to high-level frameworks.