Skip to content

Working with obfuscated code

Lukas22041 edited this page Oct 15, 2026 · 3 revisions

Some of Starsector's source code is provided in starfarer.api.zip in the game's starsector-core folder. However, not everything in the game is provided here. This zip contains most of the API interfaces and many implementations of them that modders can work with and take as reference material.

Many of the internal systems, as well as some implementations of certain interfaces, are obfuscated within starfarer_obf.jar. You can unzip the jar and inspect its .class files with a decompiler (IntelliJ has one built in). Obfuscation is when the original code is put through an algorithm that makes the code much more difficult to comprehend and follow.

 public void stopFiring() {
        if (this.ÒÖôo00.oo0000() != OOoO.Ô00000 || this.isInBurst()) {
            this.ÒÖôo00.float.super();
        }
    }

This is a method taken from one implementation of WeaponAPI that can be found in the obfuscated jar. You can see that there are calls to weirdly named methods and fields like ÒÖôo00. These are nonsense names that don't show the original names.

Gray Area

This page will continue discussing the difficulties of working with this code and how to get around them. However, it has to be noted that this is, in fact, a gray area. While Alex, the game's developer, has been supportive of mods working with obfuscated code, it may still violate the game's license.

Difficulties with obfuscated code

The surface issue with obfuscated code is that, of course, it’s obfuscated and, as such, difficult to read and understand. This depends on what code you try to read, though, as some parts are "less" obfuscated than others, as will be discussed in the next section.

The real issue for mod authors is that obfuscated code is not consistent. When the game's developer obfuscates the game, they do so for different platforms (Windows, Mac, Linux), and for each version and platform, the obfuscator's algorithm is not guaranteed to produce the same result.

 //Windows, v0.98a-RC7
 this.ÒÖôo00.float.super();
    
 //Windows, v0.98a-RC8
 this.ÒÖôo00.new.super();

 //Linux, v0.98a-RC7
 this.ooOo.float.float();

The example above displays how the exact same line of code (taken from the stopFiring example) may differ between versions and platforms. This is an issue because when you compile your mod, a hard reference to it is created. When someone uses your mod on a slightly different minor version or another platform, it will cause a crash.

Hard references like those would require creating a release of your mod compiled for each platform separately and having a release for every single hotfix version of the game.

The way around this issue is Reflection. Instead of having a compile-time reference to something, you inspect the runtime state and find things based on non-obfuscated identifiers.

Note that this issue can be caused by anything within the method being obfuscated, whether that is the return type's name, the method name or a parameter type's name. It’s not just about the method name being obfuscated, but anything that identifies that method.

Non-obfuscated code in the obfuscated jar

While the game is obfuscated, it is not fully obfuscated. If we look back to the earlier stopFiring method, it retains its original name and is quite easy to grasp. In fact, despite the code itself being unintelligible, the method name still tells us what the method does, and as such we may even deduce what the obfuscated code does.

This is because there are certain things the obfuscator cannot obfuscate without breaking the function of the code, so those are skipped. These are the most common cases:

  • A class that implements DoNotObfuscate. The game commonly uses this for classes that are included in the game's save. This is because otherwise, similar to our hard-reference issue, those classes would potentially gain different names between versions, and the save loading could not identify what it has to restore.
  • A class that implements an API interface. This is where the earlier stopFiring example falls. Any method inherited from an interface cannot be obfuscated and keeps its original name and parameters, as otherwise Java would not be able to find what it inherits from.
  • Usage of classes that exist in the API. The obfuscated code may refer to things like Vector2f. Those types can't be obfuscated since they come from another package, and methods with return types/parameters like those are stable.

Patching such code

To patch classes like those, you need to make good use of PatchLib’s matchers. They provide tools for identifying a patch target in a variety of ways. The following code block, for example, identifies the admin UI panel by the package that it is in and by the fact that it has a method called getAdminData. It has to do this as the class name itself is obfuscated.

@Patch(target = @ClassMatch(packageName = "com.fs.starfarer.campaign.command",
        methodMatches = { @MethodMatch(methodName = "getAdminData") }
))
class AdminPanelPatch() { }

If, for example, we also have a method with an obfuscated name and two obfuscated parameters but a non-obfuscated Vector2f return type, we could target it by matching a method with two parameters and the stable return type.

@After(target = @MethodMatch(parameterCount = 2, returnType = Vector2f.class))
public static void after() { }

You should ensure that you only match the method that you are interested in patching; a parameterCount = 2 condition alone could potentially match many more methods.

Risks

Patching the game's internals can allow you to do things that would otherwise not be possible, but it is worth noting that, unlike the game's API, there is no guarantee that things won't change or even change dramatically. The game's developer tries to keep breaking changes to a minimum in the API, but the same cannot be expected for this section of code.

Clone this wiki locally