/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.tools.obfuscation; import java.io.File; import java.util.Collection; import java.util.List; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import org.spongepowered.asm.mixin.injection.struct.MemberInfo; import org.spongepowered.asm.obfuscation.mapping.common.MappingField; import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; import org.spongepowered.asm.util.ObfuscationUtil; import org.spongepowered.asm.util.ObfuscationUtil.IClassRemapper; import org.spongepowered.tools.obfuscation.mapping.IMappingConsumer; import org.spongepowered.tools.obfuscation.mapping.IMappingProvider; import org.spongepowered.tools.obfuscation.mapping.IMappingWriter; import org.spongepowered.tools.obfuscation.mapping.IMappingConsumer.MappingSet; import org.spongepowered.tools.obfuscation.mirror.TypeHandle; import org.spongepowered.tools.obfuscation.interfaces.IObfuscationEnvironment; import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor; /** * Provides access to information relevant to a particular obfuscation * environment. * * <p>We classify different types of possible obfuscation (eg. "searge", * "notch") as <em>obfuscation environments</em> and store related information * such as the input mappings here.</p> */ public abstract class ObfuscationEnvironment implements IObfuscationEnvironment { /** * Remapping proxy for remapping descriptors */ final class RemapperProxy implements IClassRemapper { @Override public String map(String typeName) { if (ObfuscationEnvironment.this.mappingProvider == null) { return null; } return ObfuscationEnvironment.this.mappingProvider.getClassMapping(typeName); } @Override public String unmap(String typeName) { if (ObfuscationEnvironment.this.mappingProvider == null) { return null; } return ObfuscationEnvironment.this.mappingProvider.getClassMapping(typeName); } } /** * Type */ protected final ObfuscationType type; /** * Mapping provider */ protected final IMappingProvider mappingProvider; protected final IMappingWriter mappingWriter; protected final RemapperProxy remapper = new RemapperProxy(); /** * Annotation processor */ protected final IMixinAnnotationProcessor ap; /** * Name of the resource to write generated mappings to */ protected final String outFileName; /** * File containing the source mappings */ protected final List<String> inFileNames; /** * True once we've tried to initialise the mappings, initially false so that * we can do mapping init lazily */ private boolean initDone; protected ObfuscationEnvironment(ObfuscationType type) { this.type = type; this.ap = type.getAnnotationProcessor(); this.inFileNames = type.getInputFileNames(); this.outFileName = type.getOutputFileName(); this.mappingProvider = this.getMappingProvider(this.ap, this.ap.getProcessingEnvironment().getFiler()); this.mappingWriter = this.getMappingWriter(this.ap, this.ap.getProcessingEnvironment().getFiler()); } @Override public String toString() { return this.type.toString(); } protected abstract IMappingProvider getMappingProvider(Messager messager, Filer filer); protected abstract IMappingWriter getMappingWriter(Messager messager, Filer filer); private boolean initMappings() { if (!this.initDone) { this.initDone = true; if (this.inFileNames == null) { this.ap.printMessage(Kind.ERROR, "The " + this.type.getConfig().getInputFileOption() + " argument was not supplied, obfuscation processing will not occur"); return false; } int successCount = 0; for (String inputFileName : this.inFileNames) { File inputFile = new File(inputFileName); try { if (inputFile.isFile()) { this.ap.printMessage(Kind.NOTE, "Loading " + this.type + " mappings from " + inputFile.getAbsolutePath()); this.mappingProvider.read(inputFile); successCount++; } } catch (Exception ex) { ex.printStackTrace(); } } if (successCount < 1) { this.ap.printMessage(Kind.ERROR, "No valid input files for " + this.type + " could be read, processing may not be sucessful."); this.mappingProvider.clear(); } } return !this.mappingProvider.isEmpty(); } /** * Get the type */ public ObfuscationType getType() { return this.type; } /** * Get an obfuscation mapping for a method */ @Override public MappingMethod getObfMethod(MemberInfo method) { MappingMethod obfd = this.getObfMethod(method.asMethodMapping()); if (obfd != null || !method.isFullyQualified()) { return obfd; } // Get a type handle for the declared method owner TypeHandle type = this.ap.getTypeProvider().getTypeHandle(method.owner); if (type == null || type.isImaginary()) { return null; } // See if we can get the superclass from the reference TypeMirror superClass = type.getElement().getSuperclass(); if (superClass.getKind() != TypeKind.DECLARED) { return null; } // Well we found it, let's inflect the class name and recurse the search String superClassName = ((TypeElement)((DeclaredType)superClass).asElement()).getQualifiedName().toString(); return this.getObfMethod(new MemberInfo(method.name, superClassName.replace('.', '/'), method.desc, method.matchAll)); } /** * Get an obfuscation mapping for a method */ @Override public MappingMethod getObfMethod(MappingMethod method) { return this.getObfMethod(method, true); } /** * Get an obfuscation mapping for a method */ @Override public MappingMethod getObfMethod(MappingMethod method, boolean lazyRemap) { if (this.initMappings()) { boolean remapped = true; MappingMethod originalMethod = method.copy(); MappingMethod methodMapping = this.mappingProvider.getMethodMapping(method); // If no obf mapping, we can attempt to remap the owner class if (methodMapping == null) { if (lazyRemap) { return null; } methodMapping = originalMethod; remapped = false; } String remappedOwner = this.getObfClass(methodMapping.getOwner()); if (remappedOwner == null || remappedOwner.equals(method.getOwner()) || remappedOwner.equals(methodMapping.getOwner())) { return remapped ? methodMapping : null; } if (remapped) { return methodMapping.move(remappedOwner); } String desc = ObfuscationUtil.mapDescriptor(methodMapping.getDesc(), this.remapper); return new MappingMethod(remappedOwner, methodMapping.getSimpleName(), desc); } return null; } /** * Remap only the owner and descriptor of the specified method * * @param method method to remap * @return remapped method or null if no remapping occurred */ @Override public MemberInfo remapDescriptor(MemberInfo method) { boolean transformed = false; String owner = method.owner; if (owner != null) { String newOwner = this.remapper.map(owner); if (newOwner != null) { owner = newOwner; transformed = true; } } String desc = method.desc; if (desc != null) { String newDesc = ObfuscationUtil.mapDescriptor(method.desc, this.remapper); if (!newDesc.equals(method.desc)) { desc = newDesc; transformed = true; } } return transformed ? new MemberInfo(method.name, owner, desc, method.matchAll) : null; } /** * Remap a single descriptor in the context of this environment * * @param desc descriptor to remap * @return remapped descriptor, may return the original descriptor if no * remapping occurred */ @Override public String remapDescriptor(String desc) { return ObfuscationUtil.mapDescriptor(desc, this.remapper); } /** * Get an obfuscation mapping for a field */ @Override public MappingField getObfField(MemberInfo field) { return this.getObfField(field.asFieldMapping(), true); } /** * Get an obfuscation mapping for a field */ @Override public MappingField getObfField(MappingField field) { return this.getObfField(field, true); } /** * Get an obfuscation mapping for a field */ @Override public MappingField getObfField(MappingField field, boolean lazyRemap) { if (!this.initMappings()) { return null; } MappingField fieldMapping = this.mappingProvider.getFieldMapping(field); // If no obf mapping, we can attempt to remap the owner class if (fieldMapping == null) { if (lazyRemap) { return null; } fieldMapping = field; } String remappedOwner = this.getObfClass(fieldMapping.getOwner()); if (remappedOwner == null || remappedOwner.equals(field.getOwner()) || remappedOwner.equals(fieldMapping.getOwner())) { return fieldMapping != field ? fieldMapping : null; } return fieldMapping.move(remappedOwner); } /** * Get an obfuscation mapping for a class */ @Override public String getObfClass(String className) { if (!this.initMappings()) { return null; } return this.mappingProvider.getClassMapping(className); } /** * Write out generated mappings */ @Override public void writeMappings(Collection<IMappingConsumer> consumers) { MappingSet<MappingField> fields = new MappingSet<MappingField>(); MappingSet<MappingMethod> methods = new MappingSet<MappingMethod>(); for (IMappingConsumer mappings : consumers) { fields.addAll(mappings.getFieldMappings(this.type)); methods.addAll(mappings.getMethodMappings(this.type)); } this.mappingWriter.write(this.outFileName, this.type, fields, methods); } }