Class Oaktree

java.lang.Object
de.geolykt.starloader.deobf.Oaktree

public class Oaktree extends Object
Primitive class metadata recovery tool. Originally intended for SML0 (a patch-based modding framework intended for larger tasks like multiplayer) but got recycled to fix the mess produced by the remapping software and to make it ready for decompilation.
Author:
Geolykt
  • Field Details

    • COLLECTIONS

      public static final Set<String> COLLECTIONS
      A hardcoded set of implementations of the Collection interface that apply for generics checking later on.
    • ITERABLES

      public static final Set<String> ITERABLES
      A hardcoded set of implementations of the Iterable interface that apply for generics checking later on.
    • JAVA_KEYWORDS

      public static final Set<String> JAVA_KEYWORDS
    • VISIBILITY_MODIFIERS

      public static final int VISIBILITY_MODIFIERS
      See Also:
  • Constructor Details

    • Oaktree

      public Oaktree()
    • Oaktree

      public Oaktree(ClassLoader classWrapperClassloader)
  • Method Details

    • getReturnedClass

      @Nullable @Contract(pure=true) public static final @Nullable String getReturnedClass(String methodDesc)
      Obtains the internal name of the class that is returned by a given method descriptor. If not an object (or array) is returned as according to the method descriptor, null is returned by this method. For arrays, the leading "[" are removed - so it is not possible to differ between array and "normal" object.

      This method was created in anticipation of L-World.

      Parameters:
      methodDesc - The method descriptor
      Returns:
      The internal name of the object or array returned by the method.
    • main

      public static void main(@NotNull @NotNull String[] args)
    • applyInnerclasses

      public void applyInnerclasses()
      Applies the inner class nodes to any encountered classes.
    • definalizeAnonymousClasses

      public void definalizeAnonymousClasses()
      Removes the final access modifier from non-obfuscated anonymous classes. The reason this is done is because for recompiled galimulator (using Java 17 to compile and target 1.8), the access modifiers differ in this instance. Why exactly this is the case is unknown to me.
    • fixComparators

      public void fixComparators(boolean resolveTRArtifact)
      Add the signature of obvious bridge methods (i. e. comparators).
      Parameters:
      resolveTRArtifact - Whether to resolve an artifact left over by tiny remapper.
    • fixForeachOnArray

      public int fixForeachOnArray()
      Resolve useless <unknown> mentions when quiltflower decompiles enhanced for loops that loop on arrays by adding their respective LVT entries via guessing. However since this requires the knowledge of the array type, this may not always be successful.
      As this modifies the LVT entries, it should be called AFTER fixParameterLVT().
      Returns:
      The amount of added LVTs
    • fixInnerClasses

      public void fixInnerClasses()
      Guesses the inner classes from class nodes
    • fixParameterLVT

      public void fixParameterLVT()
      Method that tries to put the Local Variable Table (LVT) in a acceptable state by synchronising parameter declarations with lvt declarations. Does not do anything to the LVT if the LVT is declared but empty, which is a sign of the usage of obfuscation tools. It is intended to be used in combination with decompilers such as quiltflower but might not be useful for less naive decompilers such as procyon, which do not decompile into incoherent java code if the LVT is damaged.
    • fixSwitchMaps

      public int fixSwitchMaps()
      Method that tries to restore the SwitchMaps to how they should be. This includes marking the SwitchMap classes as anonymous classes, so it is likely that they cannot be referenced afterwards.

      It may prove useful to call methods such as guessAnonymousInnerClasses() or guessLocalClasses() BEFORE calling this method, as it needs to find the outermost class of all classes. However if a class is not marked as an inner class of another class, then conflicts may occur as the SwitchMa would be an inner class of both classes. This state is nonsensical and hence warnings will be printed if it is encountered.

      Returns:
      The amount of classes who were identified as switch maps.
    • getClassNodesDirectly

      public List<org.objectweb.asm.tree.ClassNode> getClassNodesDirectly()
    • guessAnonymousClasses

      @Contract(value="-> new", pure=true) public Map<String,MethodReference> guessAnonymousClasses()
      Guesses the should-be inner classes of classes based on the usages of the class. This only guesses anonymous classes based on the code, but not based on the name. It also does not require the synthetic fields for the anonymous classes, so it may do mismatches from time to time.

      It will not overwrite already existing relations and will not perform any changes to the class nodes. Inner class nodes must therefore be applied manually. It is advisable to run applyInnerclasses() afterwards.

      Returns:
      The returned map will have the outer class as the key and the outer method as the desc.
    • analyseLikelyMethodReturnCollectionGenerics

      public Map<MethodReference,ClassWrapper> analyseLikelyMethodReturnCollectionGenerics()
      Analyses the likely generic type of the return value of methods. The return value may only be an instance of Collection or an implementation of it (as defined through ClassWrapperPool.isImplementingInterface(ClassWrapper, String)).

      To reduce the imprecision of the used algorithm following filters are put in place:

      • The method must be static
      • The method must not already have a signature
      • The method must not have arguments
      Due to these constraints only few methods will match. Furthermore this method internally uses rather expensive tools such as the StackWalker and the ClassWrapperPool, which means using this method may not always be beneficial.

      This method does not modify the signatures of methods directly. This operation would need to be done by an external method.

      Returns:
      A map that stores the location of the method as the key and the signature type as the value.
    • getProposedLocalClassNames

      public Map<String,String> getProposedLocalClassNames(String localClassNamePrefix, BiPredicate<String,String> condition, boolean applyInnerClassNodes)
      Guesses which classes are a local class within a certain class. Other than anonymous classes, local classes can be references within the entire block. This method only supports guessing local classes within classes (may they be nested or not), however can also lead to it marking anonymous classes as local classes.

      The names of the local classes are inferred from outer class, the given prefix and their name compared to other local classes.

      This method does not check for collision

      This method will internally allow for large amounts of nesting - at performance cost, however should a recursive nest be assumed, the nest (and all nests depending on the nest) will be silently discarded.

      Parameters:
      localClassNamePrefix - The prefix to use for naming local classes
      condition - A predicate that is called for each proposed mapping. First argument is the outer class, second argument the inner class. Should the predicate yield false, the relation is discarded.
      applyInnerClassNodes - Whether to apply InnerClassNode to the inner AND outer class nodes. Making it false may require fixInnerClasses() to be applied.
      Returns:
      A map storing proposed renames of the inner classes.
    • guessAnonymousInnerClasses

      public int guessAnonymousInnerClasses()
      Guesses anonymous inner classes by checking whether they have a synthetic field and if they do whether they are referenced only by a single "parent" class. Note: this method is VERY aggressive when it comes to adding inner classes, sometimes it adds inner classes on stuff where it wouldn't belong. This means that usage of this method should be done wisely. This method will do some damage even if it does no good.
      Returns:
      The amount of guessed anonymous inner classes
    • guessFieldGenerics

      public int guessFieldGenerics()
      Guesses the generic signatures of fields based on their usage. This might be inaccurate under some circumstances, however it tries to play it as safe as possible. The main algorithm in this method checks for generics via foreach iteration over the field and searches for the CHECKCAST to determine the signature. Note: this method should be invoked AFTER fixParameterLVT(), invoking this method before it would lead to LVT fixing not working properly
      Returns:
      The amount of added field signatures
    • guessLocalClasses

      public Map<String,String> guessLocalClasses()
      Guesses which classes are local classes within another class. Local classes are non-static classes that are nested within another class or method.

      This method is rather aggressive and may recommend pairing that do not make sense. It is up to the API consumer to sort out nonsensical pairings from the ones that make sense.

      Invoking this method does not have any effect in itself. The returned map should be used to create pairings.

      Returns:
      A map that has the inner classes as the key and their outer class as it's value.
    • index

      public void index(JarFile file)
    • inferConstructorGenerics

      public void inferConstructorGenerics()
      Infers the generics of constructors based on the calls to the constructor.
    • inferMethodGenerics

      public int inferMethodGenerics()
      Infers the generic signatures of methods based on the contents of the method.
      Returns:
      The amount of guessed signatures
    • invalidateNameCaches

      public void invalidateNameCaches()
      Invalidate internal ClassNode name caches. Should be invoked when for example class nodes are remapped, at which point internal caches are no longer valid.
    • lambdaStreamGenericSignatureGuessing

      public void lambdaStreamGenericSignatureGuessing(Map<FieldReference,ClassWrapper> fields, Map<MethodReference,ClassWrapper> methods)
    • write

      public void write(OutputStream out) throws IOException
      Throws:
      IOException
    • write

      public void write(@NotNull @NotNull OutputStream out, @NotNull @NotNull Path resources) throws IOException
      Write all class nodes and copy over all resources from a given jar. This method throws an IOException if there is no file at the path "resources" or if it is not a zip (and by extension jar) file. If no resources need to be copied over, write(OutputStream) should be used instead.
      Parameters:
      out - The stream to write the nodes and resources to as a jar
      resources - The path to obtain resources from
      Throws:
      IOException - If something went wrong while writing to the stream or reading the resources jar.