Skip to content

Reflection

Lukas22041 edited this page Oct 15, 2026 · 5 revisions

In some cases, you will need access to methods and fields from the original class you are patching. Let’s say, for one of our hullmod patches, we want to access the spec field in BaseHullMod, the subtype of all the classes we are patching. We simply want to add a tooltip that appends the name of the hullmod.

To get access to the field, you can usually make use of context.getSelf(). This method provides you with the instance of the object you are patching, which would hold the spec variable. However, in this case, you will encounter an error.

@Patch(target = @ClassMatch(subtype = BaseHullMod.class))
public class HullmodPatch {

    @After(target = @MethodMatch(methodName = "addPostDescriptionSection"))
    public static void afterAddPostDescriptionSection(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        BaseHullMod self = (BaseHullMod) context.getSelf();
        HullModSpecAPI spec = self.spec;
                              //ERROR: 'spec' has protected access in 
                              // 'com.fs.starfarer.api.combat.BaseHullMod'
        
        tooltip.addPara("Added tooltip to " + spec.getDisplayName(),
                0f, Misc.getTextColor(), Misc.getHighlightColor());

    }
}

If we take a look at the code of BaseHullMod, we can see why. The spec field has a protected access modifier, making it only accessible from subclasses or classes in the same package.

public class BaseHullMod implements HullModEffect {

    protected HullModSpecAPI spec;

    // < Rest of the class >
}

If you remember the How patching works page, this might confuse you. After all, PatchLib inserts your code into your target method, right? Shouldn't this make your code count as part of the class?

But not quite, for two reasons:

  • PatchLib does not insert your code directly, it inserts its own dispatchers, which then call your code.
  • Even if direct insertion was the case, the compiler has no idea that your code would eventually be part of another class, and would throw the error anyway.

So direct access to the field is not possible here, but that’s where Reflection comes into play.

Reflection in programming usually refers to the capability of code to inspect and manipulate itself. With it, you can access any private or protected method and field. However, Starsector blocks Reflection by default. There are multiple ways around this, but for convenience, PatchLib provides a small Reflection library that you can use within patches.

Reflection Utilities

The reflection utilities are accessed through the context parameter. There are four different methods, but each one has a bunch of overloads:

  • getMethod()
  • hasMethod()
  • getField()
  • hasField()

For now, getMethod() and getField() are the most relevant to us. The other two are more useful if you work with obfuscated code.

The getField() methods return a Ref. A ref is just a typed wrapper around the variable, which lets you call .get or .set on it. The most basic way to make use of the getField call is to simply provide the name of the field you want to retrieve.

@Patch(target = @ClassMatch(subtype = BaseHullMod.class))
public class HullmodPatch {

    @After(target = @MethodMatch(methodName = "addPostDescriptionSection"))
    public static void afterAddPostDescriptionSection(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        Ref<HullModSpecAPI> specRef = context.<HullModSpecAPI>getField("spec");
        HullModSpecAPI spec = specRef.get();

        tooltip.addPara("Added tooltip to " + spec.getDisplayName(),
                0f, Misc.getTextColor(), Misc.getHighlightColor());

    }
}

In many cases, the Ref is overkill, though. Oftentimes, you only want to retrieve the value and nothing more. You can simply call it like the following in those cases:

    HullModSpecAPI spec = (HullModSpecAPI) context.getField("spec").get();

By default, the reflection methods are called on the instance of what you are patching, but sometimes you may want to use reflection on another object. In those cases, simply provide the instance as a second parameter.

    Object someInstance = new Object();
    context.getField("someField", someInstance).get();

Queries

Sometimes you may need to select a specific field or method by properties other than its name. In those cases, you can make use of MethodQuery or FieldQuery. These are builder-pattern objects that allow you to AND together multiple requirements, similar to the patch matchers.

@Patch(target = @ClassMatch(subtype = BaseHullMod.class))
public class HullmodPatch {

    @After(target = @MethodMatch(methodName = "addPostDescriptionSection"))
    public static void afterAddPostDescriptionSection(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        FieldQuerySpec query = FieldQuery.create()
                .fieldName("spec")
                .type(HullModSpecAPI.class)
                .build();
        HullModSpecAPI spec = (HullModSpecAPI) context.getField(query).get();

        tooltip.addPara("Added tooltip to " + spec.getDisplayName(),
                0f, Misc.getTextColor(), Misc.getHighlightColor());

    }
}

You can also create the query as a private static final variable outside of your patch handler. This has performance advantages, as it avoids re-creating the query object on each call.

@Patch(target = @ClassMatch(subtype = BaseHullMod.class))
public class HullmodPatch {

    private final static FieldQuerySpec query = FieldQuery.create()
            .fieldName("spec")
            .type(HullModSpecAPI.class)
            .build();

    @After(target = @MethodMatch(methodName = "addPostDescriptionSection"))
    public static void afterAddPostDescriptionSection(AfterContext context) {
        TooltipMakerAPI tooltip = (TooltipMakerAPI) context.getArg(0);

        HullModSpecAPI spec = (HullModSpecAPI) context.getField(query).get();

        tooltip.addPara("Added tooltip to " + spec.getDisplayName(),
                0f, Misc.getTextColor(), Misc.getHighlightColor());

    }
}

Clone this wiki locally