Skip to content

Releases: CadixDev/Lorenz

0.5.8

23 Oct 22:50
014802b
Compare
Choose a tag to compare

Fixes

  • GH-53: Don't de-obfuscate field types twice, which can cause
    invalid output in some conditions.

0.5.7

01 Feb 15:12
edb2b65
Compare
Choose a tag to compare

Fixes

  • CSRG and TSRG now ignore package mappings, rather than erroneously reading them
    as class mappings.
  • MappingSet#deobfuscate(FieldType) will now correctly de-obfuscate object types
    of inner classes, where the parent class has a mapping - but the inner class
    does not.
  • GH-29: Avoid inheriting field mappings from a parent where the child class has
    a field of its own, of the same signature.

0.5.6

27 Nov 20:37
11d35e3
Compare
Choose a tag to compare

Configurable Parallelism during mapping merges

The mapping merging system introduced in 0.5.4 merges mappings in parallel - which in quick testing can
improve the merge time by half. There may be cases where the max number of threads needs to be controlled.
One common example is debugging Lorenz itself, having multiple merge threads running at once can make
debugging a lot more difficult.

This release just adds one new method to MergeConfig:
getParallelism().
Set this value using the new withParallelism()
method. Check the javadocs for more info.

Allow arbitrary indexes for parameter mappings

Method mappings may now contain parameter mappings for arbitrary indexes, rather than being constrained to
between 0 and the number of parameters in the method signature. This is nice from a general flexibility
perspective as Lorenz is only a container and isn't intended for validating mappings, but also fixes the issue
where Lorenz can't read mappings which using 1-indexed method parameters for instance methods. With this change
it's up to the user to decide how to use parameter mappings, Lorenz doesn't dictate anything one way or the
other (just like the other mapping types).

Any code which worked with Lorenz before will still continue to work, as this change only removes constraints
which used to be present. If some code was written which relied on the existing index checks then this will
technically be a breaking change, as you'll need to handle those checks yourself. That is likely to be a minor
edge case however, so this is still considered a minor release.

0.5.5

07 Nov 13:03
9d5bd9a
Compare
Choose a tag to compare

Writer configurations

Writer configurations are a powerful new feature that allows the output of
mappings writers to be fine-tuned as consumers would like. Currently, this
is limited to:

  • The sorting functions used to write mappings for classes, fields, and
    methods.
  • The API is open-ended, so there is potential for further additions to be
    made.

Writer configurations can be created fluently through use the provided
builder, which includes our default implementations.

protected MappingsWriterConfig config = MappingsWriterConfig.builder()
        .classMappingComparator(Comparator.comparing(Mapping::getFullObfuscatedName))
        .fieldMappingComparator(Comparator.comparing(Mapping::getFullObfuscatedName))
        .methodMappingComparator(Comparator.comparing(Mapping::getFullObfuscatedName))
        .build();

They are applied to a mappings writer by use of the new #setConfig method,
and can be retrieved through the corresponding #getConfig() method.

writer.setConfig(MappingsWriterConfig.builder().build());

Fixes

  • The Kin mapping writer now produces consistent output, sorting the
    mappings as per the provided comparators. Output is also now rid of 'junk'
    mappings (those without valid children mappings, or a de-obf mapping of
    their own).

0.5.4

25 Aug 12:48
69ff1cf
Compare
Choose a tag to compare

Lorenz 0.5.4 includes yet-more bug fixes, and a new (and vastly more powerful)
mapping merging API.

The ProGuard mapping reader now also reads field types.

The Bombe dependency has been bumped to 0.3.4, resolving a NullPointerException
that would occur when using the ClassLoaderClassProvider.

Merging

Lorenz now has a new mappings merging API which aims at fulfilling 3 main goals:

  1. Improve the output of the merging operation significantly, supporting many more edge cases and handling merges
    with less complete mapping sets.
  2. Allow configuring (to some degree) the merging process to control some aspects of the default merge process.
  3. Provide an API which is extensible and modifiable at multiple levels to enable custom merge situations.

There are a limited number of possible edge cases one would expect to run into when merging, so we laid them all out
and made decisions on how best to handle them in the default case. Due to the extensible API users can override and
change any of the default behavioe as necessary. The default behavior is laid out as follows:

Left Right Output Note
A -> B B -> C A -> C Typical case, easiest to handle
A -> B Missing A -> B Standalone mappings get copied
Missing B -> C B -> C Standalone mappings get copied
A -> B X -> Y A -> B and X -> Y This is no different than the 2 above cases with missing mappings on each side. This is just meant to be a further example that if two unrelated mappings are present, a standard merger won't know how to handle them other than copying both.
A -> B A -> C A -> C By default the right mapping is considered the "most up to date" mappings, so in the case where both mapping sets provide mappings for the same obfuscated name, the right mapping is used in the default implementation.
A -> B (types) B -> B (types) and A -> C (names) A -> B (types) and A -> C (names) This is an example of a special case situation where the left mapping only maps types, then the second mapping set only maps members, but from the expectation that the first mapping set was already applied. The default implementation should handle this case correctly.

How to read the above chart: Each mapping operation is made up of a left side and a right side. The chart
represents this with the directional arrow ->. Merge operations in the table apply to any individual mapping,
not the whole mapping set. That is, a field name mapping merging with another field name, or a class name merging
with another class name.

Handling merges with less complete mappings sets are what is intended by the last row.

Directionality

It may seem a natural goal for merging mapping sets is for the merge operation to be commutative - in other words, when
merging A -> B and B -> C you would get the same result as when merging C -> B and B -> A (except, reversed).

This is true for most cases, but for some special cases - most notably duplicate mappings - this property just can't
be handled in any reasonable way. In these situations where a decision has to be made regarding whether to keep one
mapping or another (and neither being a better option by itself) we use the position of the mapping as the heuristic for
which one to keep.

When merging A -> B and B -> C the names you get in the output are C. Because of this, the right side of a merge
is considered the more correct set of names (since it is the output). If the merge operation were to be reversed A
would be preferred instead. Because of this it is important to consider which direction works best for you merge.

Other Scenarios

There's also 2 other factors necessary to consider when merging mapping sets that we'll go over:

  1. Duplicate mappings are possible
  2. Field mappings can have type signature

Handling Duplicates

A duplicate mapping is when both mapping sets define a mapping for the same obfuscated member. For example, if you were
to merge the following mappings, they would be considered duplicated because they both share com.example.Example as
the obfuscated name.

com.example.Example -> com.example.OtherExample1
com.example.Example -> com.example.OtherExample2

For the default merge implementation right mappings take priority for all duplicate merge scenarios. So in this case
the mapping which would be present in the output mapping set would be:

com.example.Example -> com.example.OtherExample2

Handling Field Types

Field mappings can have types, but often don't. Because Java doesn't allow duplicate field names visible in any class
it's not always necessary to explicitly list the type of the field. The complication comes when merging mapping sets
where one mapping set does and the other doesn't contain field types. In this scenario looking up the field from one
mapping set to another in the default case by simply using the field signature would fail, as the type descriptor is
missing in one of the mapping sets.

The new merge system provides a configuration option for whether to handle this case. The default options is LOOSE -
that is, first check for the signature, but also check for just the field name and use it if the field
signature can't be found. This can be switched to STRICT where all field mappings must match signatures exactly.

Merging Code and API

The new merging system is made up of 2 main classes:

  1. MappingSetMerger
  2. MappingSetMergerHandler

The JavaDocs for each class go into considerable detail on exactly what each class is and how it's used, but here's a
quick rundown:

MappingSetMerger is the entry point for the merge operation. It's an interface with a default implementation which
can be retrieved with MappingSetMerger.create(). It does the job of walking the class hierarchy of both mapping sets
and finding the members of each side that should be merged together. It decides which members to include based on the
MergeConfig
provided in the call to create() (or just the default config if none was provided) and determines which individual
case each merge is.

MappingSetMerger is available with an overridable default implementation in case it's necessary to modify how the
above cases are decided individually. It is designed to let a user override individual methods in order to change said
logic when necessary, but the general idea is that in most cases you'll never need to modify the default implementation
of MappingSetMerger.

MappingSetMerger doesn't handle actually merging the mappings, all it does is recognize which type of merge each merge
is, and delegates the handling of that merge to MappingSetMergerHandler. This handler class has individual methods for
each mapping case, and is a fully-default interface. This means you can implement MappingSetMergerHandler and override
only the mapping cases you want to modify for your custom merge operation. The merge cases are all described by the
method names (and explained in further detail in the JavaDoc), and look like:

FieldMapping mergeFieldMappings(
    final FieldMapping left,
    final FieldMapping strictRight,
    final FieldMapping looseRight,
    final ClassMapping<?, ?> target,
    final MergeContext context
);

FieldMapping mergeDuplicateFieldMappings(
    final FieldMapping left,
    final FieldMapping strictRightDuplicate,
    final FieldMapping looseRightDuplicate,
    final FieldMapping strictRightContinuation,
    final FieldMapping looseRightContinuation,
    final ClassMapping<?, ?> target,
    final MergeContext context
);

FieldMapping addLeftFieldMapping(final FieldMapping left, final ClassMapping<?, ?> target, final MergeContext context);

FieldMapping addRightFieldMapping(final FieldMapping right, final ClassMapping<?, ?> target, final MergeContext context);

If you only need some custom logic when the right mapping set provides a mapping that the left mapping set doesn't
provide, then you could just override addRightFieldMapping with your custom logic and leave everything else default.
You wouldn't need to worry about walking the hierarchy or copying of the other members or handling any other special
cases.

This separation of concerns between determining what kind of merge each case is and actually handling the merge
operation for each merge case hopefully will allow you to easily customize mapping merges for whatever custom situation
you have.

Existing Code

Each of the existing merge() methods on each of the mapping classes have all been updated to use this new merge
system. The methods haven't been removed and are still binary compatible with any existing code you have which may be
using it. The output of the merge may differ however, as the logic has been considerably modified as described above.

One Last Thing

The default implementation of MappingSetMerger merges top-level classes in parallel. This is okay because
MappingSetMergerHandler is a stateless class, and MappingSetMerger is completely thread-safe. Due to this merge time
fell by as much as half of the previous merge time when testing against Minecraft mapping sets.

0.5.3

03 Jul 22:41
c6e3cc3
Compare
Choose a tag to compare
0.5.3 Pre-release
Pre-release

Lorenz 0.5.3 includes yet-another set of bug-fixes:

  • Mercury/GH-14: Inheritance completion now considers elevated return types
  • Bombe/GH-11: Bump Bombe dependency to 0.3.2, including a fix for remapping
    manifests without a Main-Class attribute
  • Lorenz/GH-30: Don't wrap Writers in TextMappingsWriter, allowing files
    larger than BufferedWriters buffer to be written (thanks to @phase for this
    bug-fix)
  • Lorenz/GH-32: Support blank comments in IO readers (affects SRG formats, JAM,
    and Enigma)

Support for yet-another SRG variant, XSRG, has also been introduced with this
release - the format is the same as the SRG format, though has field types
(Lorenz/GH-33).

Thanks to @phase and @DemonWav for their contributions towards this release.

Registries

Registry has been expanded to expose more of the underlying Map's data:

  • Registry#keys(), returning a Set of Strings which correspond to the
    identifier of registered values.
  • Registry#entries(), returning a Set of Map.Entrys.

Improved Mapping Merging

Mapping Merging, introduced with Lorenz 0.5.0, has always been a fairly
lacklustre implementation - dropping mappings present on the b mapping set, if
there wasn't a mapping in the a mapping set.

Thanks to @DemonWav, that has now been resolved (see Lorenz/GH-36).

0.5.2

07 May 21:02
48a0422
Compare
Choose a tag to compare
0.5.2 Pre-release
Pre-release

Lorenz 0.5.2 addresses issues with using Lorenz's ASM Remapper on Atlas >= 0.2.0.
Specifically, it should now be possibly to concurrently access mappings.

0.5.1

30 Apr 20:08
Compare
Choose a tag to compare
0.5.1 Pre-release
Pre-release

Lorenz 0.5.1 brings some minor additions, that were missed during previous dev cycles,
identified during the development of Lorenz 0.6.

ProGuard Reader

Lorenz 0.5.1 includes a lorenz-io-proguard module, that allows you to read
ProGuard output files (for example, Mojang's mapping files for Minecraft). You can
obtain the ProGuard MappingFormat with MappingFormats.byId("proguard").

Importantly, the ProGuard module does not support writing mapping files.

0.5.0

21 May 20:12
cced203
Compare
Choose a tag to compare
0.5.0 Pre-release
Pre-release

Lorenz 0.5.0 has enjoyed a long development process (beginning the 1st of September!),
and packs a bundle of cool changes. The most notable being the change of package, from
me.jamiemansfield.lorenz to org.cadixdev.lorenz.

Modularisation

The long put-off modularisation has finally arrived - with the Enigma, JAM, and Kin
mapping formats being given their own modules.

  • Enigma: org.cadixdev:lorenz-io-enigma:0.5.0
  • JAM: org.cadixdev:lorenz-io-jam:0.5.0
  • Kin: org.cadixdev:lorenz-io-kin:0.5.0

Mapping formats can be introduced through service providers, and MappingFormats will
populate a registry with all the formats found. You can get a mapping format, like the
following:

final MappingFormat enigma = MappingFormats.byId("enigma");
final MappingFormat jam    = MappingFormats.byId("jam");
final MappingFormat kin    = MappingFormats.byId("kin");

Merging and reversing

Mapping sets can now be reversed (A->B -> B->A), and mapping sets merged
(A->B + B->C = A->C). This can be achieved like the following.

// let a be a MappingSet
// let b be a MappingSet

final MappingSet reversed = a.reverse();
final MappingSet merged = a.merge(b);

Extension Data

Lorenz now supports adding extension data to the mapping model (no existing formats serialise this
information
).

static final ExtensionKey<String> EXTRA_NAME = new ExtensionKey<>(String.class, "extra_name");

final MappingSet mappings = new MappingSet();
mappings.set(EXTRA_NAME, "Beep Boop");
mappings.get(EXTRA_NAME).get(); // Beep Boop

0.4.4

15 May 16:41
ed8df44
Compare
Choose a tag to compare
0.4.4 Pre-release
Pre-release

Lorenz 0.4.4 is the combination of bugs identified in the development of Lorenz 0.5.0, specifically
with regards to the mapping format implementations, in addition to a faulty mapping model method.

IO

  • TSRG: Don't write empty class line for parents
  • TSRG: Correct a length check, preventing some valid mappings from being read
  • Enigma: Output field types properly, using the obfuscated type

Mapping Model

  • Fix the faulty implementation of ClassMapping::hasMappings, specifically it did'nt consider method
    argument mappings