diff --git a/src/main/java/net/fabricmc/tinyremapper/AsmClassRemapper.java b/src/main/java/net/fabricmc/tinyremapper/AsmClassRemapper.java index b3c244ba..cf7c7e5b 100644 --- a/src/main/java/net/fabricmc/tinyremapper/AsmClassRemapper.java +++ b/src/main/java/net/fabricmc/tinyremapper/AsmClassRemapper.java @@ -18,8 +18,10 @@ package net.fabricmc.tinyremapper; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.lang.model.SourceVersion; @@ -48,7 +50,6 @@ class AsmClassRemapper extends ClassRemapper { public AsmClassRemapper(ClassVisitor cv, AsmRemapper remapper, boolean checkPackageAccess, boolean skipLocalMapping, boolean renameInvalidLocals) { super(cv, remapper); - this.checkPackageAccess = checkPackageAccess; this.skipLocalMapping = skipLocalMapping; this.renameInvalidLocals = renameInvalidLocals; @@ -72,7 +73,33 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str methodNode = new MethodNode(api, access, name, descriptor, signature, exceptions); } - return super.visitMethod(access, name, descriptor, signature, exceptions); + String remappedDescriptor = remapper.mapMethodDesc(descriptor); + Collection names = ((AsmRemapper) remapper).mapMultiMethodName(className, name, descriptor); + MethodVisitor visitor; + + if(names.size() == 1) { + visitor = this.cv.visitMethod( + access, + names.iterator().next(), + remappedDescriptor, + remapper.mapSignature(signature, false), + exceptions == null ? null : remapper.mapTypes(exceptions)); + } else { + List visitorList = new ArrayList<>(); + + for(String newName : names) { + visitorList.add(this.cv.visitMethod( + access, + newName, + remappedDescriptor, + remapper.mapSignature(signature, false), + exceptions == null ? null : remapper.mapTypes(exceptions))); + } + + visitor = new MultiMethodVisitor(api, visitorList); + } + + return visitor == null ? null : createMethodRemapper(visitor); } @Override diff --git a/src/main/java/net/fabricmc/tinyremapper/AsmRemapper.java b/src/main/java/net/fabricmc/tinyremapper/AsmRemapper.java index b1f005c0..4baaa55d 100644 --- a/src/main/java/net/fabricmc/tinyremapper/AsmRemapper.java +++ b/src/main/java/net/fabricmc/tinyremapper/AsmRemapper.java @@ -17,6 +17,13 @@ package net.fabricmc.tinyremapper; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; import java.util.Locale; import org.objectweb.asm.commons.Remapper; @@ -53,23 +60,60 @@ public String mapFieldName(String owner, String name, String desc) { return remapper.extraRemapper != null ? remapper.extraRemapper.mapFieldName(owner, name, desc) : name; } + public Collection mapMultiMethodName(String owner, String name, String desc) { + ClassInstance cls = getClass(owner); + if (cls == null) return Collections.singletonList(name); + List instances = cls.multiResolve(MemberType.METHOD, MemberInstance.getMethodId(name, desc)); + if(instances.isEmpty()) { + return Collections.singletonList(mapMember(null, owner, name, desc)); + } else { + return new AbstractCollection() { + @Override + public Iterator iterator() { + return new Iterator() { + Iterator members = instances.iterator(); + @Override + public boolean hasNext() { + return members.hasNext(); + } + + @Override + public String next() { + return mapMember(members.next(), owner, name, desc); + } + }; + } + + @Override + public int size() { + return instances.size(); + } + }; + } + } + @Override public String mapMethodName(String owner, String name, String desc) { ClassInstance cls = getClass(owner); if (cls == null) return name; // TODO: try to map these from just the mappings?, warn if actual class is missing MemberInstance member = cls.resolve(MemberType.METHOD, MemberInstance.getMethodId(name, desc)); + return mapMember(member, owner, name, desc); + } + + protected String mapMember(MemberInstance member, String srcOwner, String srcName, String srcDesc) { String newName; if (member != null && (newName = member.getNewName()) != null) { return newName; } - assert (newName = remapper.methodMap.get(owner+"/"+MemberInstance.getMethodId(name, desc))) == null || newName.equals(name); + assert (newName = remapper.methodMap.get(srcOwner+"/"+MemberInstance.getMethodId(srcName, srcDesc))) == null || newName.equals(srcName); - return remapper.extraRemapper != null ? remapper.extraRemapper.mapMethodName(owner, name, desc) : name; + return remapper.extraRemapper != null ? remapper.extraRemapper.mapMethodName(srcOwner, srcName, srcDesc) : srcName; } + public String mapMethodNamePrefixDesc(String owner, String name, String descPrefix) { ClassInstance cls = getClass(owner); if (cls == null) return name; diff --git a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java index f085d880..dd735798 100644 --- a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java +++ b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -26,6 +27,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -218,15 +220,16 @@ void propagate(TinyRemapper remapper, MemberType type, String originatingCls, St * different branches of the hierarchy tree that were not visited before may access it. */ - if (dir == Direction.ANY || dir == Direction.UP || isVirtual && member != null && (member.access & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) == 0) { + boolean trueVirtual = isVirtual && member != null && (member.access & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) == 0; + if (dir == Direction.ANY || dir == Direction.UP || trueVirtual) { for (ClassInstance node : parents) { if (visitedUp.add(node)) { - node.propagate(remapper, type, originatingCls, idSrc, nameDst, Direction.UP, isVirtual, false, visitedUp, visitedDown); + node.propagate(remapper, type, originatingCls, idSrc, nameDst, Direction.UP, true, false, visitedUp, visitedDown); } } } - if (dir == Direction.ANY || dir == Direction.DOWN || isVirtual && member != null && (member.access & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) == 0) { + if (dir == Direction.ANY || dir == Direction.DOWN || trueVirtual) { for (ClassInstance node : children) { if (visitedDown.add(node)) { node.propagate(remapper, type, originatingCls, idSrc, nameDst, Direction.DOWN, isVirtual, false, visitedUp, visitedDown); @@ -236,6 +239,16 @@ void propagate(TinyRemapper remapper, MemberType type, String originatingCls, St } public MemberInstance resolve(MemberType type, String id) { + return resolve(type, id, null); + } + + public List multiResolve(MemberType type, String id) { + List created = new ArrayList<>(); + this.resolve(type, id, created); + return created; + } + + public MemberInstance resolve(MemberType type, String id, List multiResolve) { MemberInstance member = getMember(type, id); if (member != null) return member; @@ -244,7 +257,7 @@ public MemberInstance resolve(MemberType type, String id) { if (member == null) { // compute - member = resolve0(type, id); + member = resolve0(type, id, multiResolve); assert member != null; // put in cache @@ -255,7 +268,7 @@ public MemberInstance resolve(MemberType type, String id) { return member != nullMember ? member : null; } - private MemberInstance resolve0(MemberType type, String id) { + private MemberInstance resolve0(MemberType type, String id, List multiResolve) { boolean isField = type == MemberType.FIELD; Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); Deque queue = new ArrayDeque<>(); @@ -274,9 +287,15 @@ private MemberInstance resolve0(MemberType type, String id) { for (ClassInstance parent : cls.parents) { if (parent.isInterface() == isField && visited.add(parent)) { MemberInstance ret = parent.getMember(type, id); - if (ret != null) return ret; - - queue.addLast(parent); + if (ret != null) { + if(multiResolve == null) { + return ret; + } else { + multiResolve.add(ret); + } + } else { + queue.addLast(parent); + } } } } while ((cls = queue.pollLast()) != null); @@ -310,7 +329,12 @@ private MemberInstance resolve0(MemberType type, String id) { if (!isField && (parentMember.access & (Opcodes.ACC_ABSTRACT)) != 0) { secondaryMatch = parentMember; } else { - return parentMember; + if(multiResolve == null) { + return parentMember; + } else { + multiResolve.add(parentMember); + continue; + } } } } @@ -321,7 +345,12 @@ private MemberInstance resolve0(MemberType type, String id) { } while (!isField && (cls = queue.pollFirst()) != null); } while ((context = queue.pollFirst()) != null); // overall-recursion for fields - return secondaryMatch != null ? secondaryMatch : nullMember; + if(multiResolve == null) { + return secondaryMatch != null ? secondaryMatch : nullMember; + } else { + multiResolve.add(secondaryMatch); + return nullMember; + } } public MemberInstance resolvePartial(MemberType type, String name, String descPrefix) { diff --git a/src/main/java/net/fabricmc/tinyremapper/MetaInfFixer.java b/src/main/java/net/fabricmc/tinyremapper/MetaInfFixer.java new file mode 100644 index 00000000..f77378a4 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/MetaInfFixer.java @@ -0,0 +1,128 @@ +package net.fabricmc.tinyremapper; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Iterator; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +public class MetaInfFixer implements OutputConsumerPath.ResourceRemapper { + public static final MetaInfFixer INSTANCE = new MetaInfFixer(); + + protected MetaInfFixer() {} + + @Override + public boolean canTransform(TinyRemapper remapper, Path relativePath) { + return shouldStripForFixMeta(relativePath) || + relativePath.getFileName().toString().equals("MANIFEST.MF") || + (remapper != null && relativePath.getNameCount() == 3 && relativePath.getName(1).toString().equals("services")); + } + + @Override + public void transform(Path destinationDirectory, Path relativePath, InputStream input, TinyRemapper remapper) throws IOException { + String fileName = relativePath.getFileName().toString(); + if (fileName.equals("MANIFEST.MF")) { + Manifest manifest = new Manifest(input); + fixManifest(manifest, remapper); + try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(destinationDirectory.resolve(relativePath)))) { + manifest.write(os); + } + } else if (remapper != null && relativePath.getNameCount() == 3 && relativePath.getName(1).toString().equals("services")) { + fileName = mapFullyQualifiedClassName(fileName, remapper); + Path newFile = destinationDirectory.resolve(relativePath).getParent().resolve(fileName); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + BufferedWriter writer = Files.newBufferedWriter(newFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { + fixServiceDecl(reader, writer, remapper); + } + } + } + + private static boolean shouldStripForFixMeta(Path file) { + if (file.getNameCount() != 2) return false; // not directly inside META-INF dir + + assert file.getName(0).toString().equals("META-INF"); + + String fileName = file.getFileName().toString(); + + // https://docs.oracle.com/en/java/javase/12/docs/specs/jar/jar.html#signed-jar-file + return fileName.endsWith(".SF") + || fileName.endsWith(".DSA") + || fileName.endsWith(".RSA") + || fileName.startsWith("SIG-"); + } + + private static String mapFullyQualifiedClassName(String name, TinyRemapper remapper) { + assert name.indexOf('/') < 0; + + return remapper.mapClass(name.replace('.', '/')).replace('/', '.'); + } + + private static void fixManifest(Manifest manifest, TinyRemapper remapper) { + Attributes mainAttrs = manifest.getMainAttributes(); + + if (remapper != null) { + String val = mainAttrs.getValue(Attributes.Name.MAIN_CLASS); + if (val != null) mainAttrs.put(Attributes.Name.MAIN_CLASS, mapFullyQualifiedClassName(val, remapper)); + + val = mainAttrs.getValue("Launcher-Agent-Class"); + if (val != null) mainAttrs.put("Launcher-Agent-Class", mapFullyQualifiedClassName(val, remapper)); + } + + mainAttrs.remove(Attributes.Name.SIGNATURE_VERSION); + + for (Iterator it = manifest.getEntries().values().iterator(); it.hasNext(); ) { + Attributes attrs = it.next(); + + for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); ) { + Attributes.Name attrName = (Attributes.Name) it2.next(); + String name = attrName.toString(); + + if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) { + it2.remove(); + } + } + + if (attrs.isEmpty()) it.remove(); + } + } + + private static void fixServiceDecl(BufferedReader reader, BufferedWriter writer, TinyRemapper remapper) throws IOException { + String line; + + while ((line = reader.readLine()) != null) { + int end = line.indexOf('#'); + if (end < 0) end = line.length(); + + // trim start+end to skip ' ' and '\t' + + int start = 0; + char c; + + while (start < end && ((c = line.charAt(start)) == ' ' || c == '\t')) { + start++; + } + + while (end > start && ((c = line.charAt(end - 1)) == ' ' || c == '\t')) { + end--; + } + + if (start == end) { + writer.write(line); + } else { + writer.write(line, 0, start); + writer.write(mapFullyQualifiedClassName(line.substring(start, end), remapper)); + writer.write(line, end, line.length() - end); + } + + writer.newLine(); + } + } +} diff --git a/src/main/java/net/fabricmc/tinyremapper/MetaInfRemover.java b/src/main/java/net/fabricmc/tinyremapper/MetaInfRemover.java new file mode 100644 index 00000000..62b1ac78 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/MetaInfRemover.java @@ -0,0 +1,19 @@ +package net.fabricmc.tinyremapper; + +import java.io.InputStream; +import java.nio.file.Path; + +public class MetaInfRemover implements OutputConsumerPath.ResourceRemapper { + public static final MetaInfRemover INSTANCE = new MetaInfRemover(); + + protected MetaInfRemover() {} + + @Override + public boolean canTransform(TinyRemapper remapper, Path relativePath) { + return relativePath.startsWith("META-INF") && relativePath.getNameCount() != 2; + } + + @Override + public void transform(Path destinationDirectory, Path relativePath, InputStream input, TinyRemapper remapper) { + } +} diff --git a/src/main/java/net/fabricmc/tinyremapper/MultiMethodVisitor.java b/src/main/java/net/fabricmc/tinyremapper/MultiMethodVisitor.java new file mode 100644 index 00000000..a5a33c87 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/MultiMethodVisitor.java @@ -0,0 +1,289 @@ +package net.fabricmc.tinyremapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.TypePath; + +public class MultiMethodVisitor extends MethodVisitor { + // find : super.(.*) + // replace: for(MethodVisitor visitor : this.visitors) {\n\t\t\tvisitor.$1\n\t\t} + + private final Collection visitors; + private final int api; + + public MultiMethodVisitor(int api, Collection visitors) { + super(api); + this.api = api; + this.visitors = visitors; + } + + @Override + public void visitParameter(String name, int access) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitParameter(name, access); + } + } + + public static class MultiAnnotationVisitor extends AnnotationVisitor { + private final List annotations; + private final int api; + public MultiAnnotationVisitor(int api, Collection input, Function function) { + super(api); + this.api = api; + this.annotations = new ArrayList<>(input.size()); + for(T visitor : input) { + this.annotations.add(function.apply(visitor)); + } + } + + @Override + public void visit(String name, Object value) { + for(AnnotationVisitor annotation : this.annotations) { + annotation.visit(name, value); + } + } + + @Override + public void visitEnum(String name, String descriptor, String value) { + for(AnnotationVisitor annotation : this.annotations) { + annotation.visitEnum(name, descriptor, value); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + return new MultiAnnotationVisitor(this.api, this.annotations, a -> a.visitAnnotation(name, descriptor)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new MultiAnnotationVisitor(this.api, this.annotations, a -> a.visitArray(name)); + } + + @Override + public void visitEnd() { + for(AnnotationVisitor annotation : this.annotations) { + annotation.visitEnd(); + } + } + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return new MultiAnnotationVisitor(this.api, this.visitors, MethodVisitor::visitAnnotationDefault); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return new MultiAnnotationVisitor(this.api, this.visitors, v -> v.visitAnnotation(descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return new MultiAnnotationVisitor(this.api, this.visitors, v -> v.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) { + return new MultiAnnotationVisitor(this.api, visitors, v -> v.visitParameterAnnotation(parameter, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return new MultiAnnotationVisitor(this.api, this.visitors, v -> v.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, + Label[] start, + Label[] end, + int[] index, + String descriptor, + boolean visible) { + return new MultiAnnotationVisitor(this.api, this.visitors, v -> v.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible)); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + return new MultiAnnotationVisitor(this.api, this.visitors, v -> v.visitInsnAnnotation(typeRef, typePath, descriptor, visible)); + } + + @Override + public void visitAnnotableParameterCount(int parameterCount, boolean visible) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitAnnotableParameterCount(parameterCount, visible); + } + } + + + @Override + public void visitAttribute(Attribute attribute) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitAttribute(attribute); + } + } + + @Override + public void visitCode() { + for(MethodVisitor visitor : this.visitors) { + visitor.visitCode(); + } + } + + @Override + public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitFrame(type, numLocal, local, numStack, stack); + } + } + + @Override + public void visitInsn(int opcode) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitInsn(opcode); + } + } + + @Override + public void visitIntInsn(int opcode, int operand) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitIntInsn(opcode, operand); + } + } + + @Override + public void visitVarInsn(int opcode, int var) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitVarInsn(opcode, var); + } + } + + @Override + public void visitTypeInsn(int opcode, String type) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitTypeInsn(opcode, type); + } + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitFieldInsn(opcode, owner, name, descriptor); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitMethodInsn(opcode, owner, name, descriptor); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + + @Override + public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitJumpInsn(opcode, label); + } + } + + @Override + public void visitLabel(Label label) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitLabel(label); + } + } + + @Override + public void visitLdcInsn(Object value) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitLdcInsn(value); + } + } + + @Override + public void visitIincInsn(int var, int increment) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitIincInsn(var, increment); + } + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + @Override + public void visitMultiANewArrayInsn(String descriptor, int numDimensions) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitTryCatchBlock(start, end, handler, type); + } + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + + + @Override + public void visitLineNumber(int line, Label start) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitLineNumber(line, start); + } + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + for(MethodVisitor visitor : this.visitors) { + visitor.visitMaxs(maxStack, maxLocals); + } + } + + @Override + public void visitEnd() { + for(MethodVisitor visitor : this.visitors) { + visitor.visitEnd(); + } + } +} diff --git a/src/main/java/net/fabricmc/tinyremapper/NonClassCopyMode.java b/src/main/java/net/fabricmc/tinyremapper/NonClassCopyMode.java index 1d63f539..6ec55b91 100644 --- a/src/main/java/net/fabricmc/tinyremapper/NonClassCopyMode.java +++ b/src/main/java/net/fabricmc/tinyremapper/NonClassCopyMode.java @@ -17,8 +17,18 @@ package net.fabricmc.tinyremapper; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + public enum NonClassCopyMode { - UNCHANGED, - FIX_META_INF, - SKIP_META_INF + UNCHANGED(), + FIX_META_INF(MetaInfFixer.INSTANCE), + SKIP_META_INF(MetaInfRemover.INSTANCE); + + public final List remappers; + + NonClassCopyMode(OutputConsumerPath.ResourceRemapper...remappers) { + this.remappers = Collections.unmodifiableList(Arrays.asList(remappers)); + } } diff --git a/src/main/java/net/fabricmc/tinyremapper/OutputConsumerPath.java b/src/main/java/net/fabricmc/tinyremapper/OutputConsumerPath.java index f5262bd1..5135e383 100644 --- a/src/main/java/net/fabricmc/tinyremapper/OutputConsumerPath.java +++ b/src/main/java/net/fabricmc/tinyremapper/OutputConsumerPath.java @@ -17,14 +17,11 @@ package net.fabricmc.tinyremapper; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; +import java.io.BufferedInputStream; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileAlreadyExistsException; @@ -34,18 +31,15 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import java.util.function.Predicate; -import java.util.jar.Attributes; -import java.util.jar.Manifest; public class OutputConsumerPath implements BiConsumer, Closeable { public static class Builder { @@ -132,18 +126,27 @@ public void addNonClassFiles(Path srcDir, boolean closeFs) throws IOException { } public void addNonClassFiles(Path srcFile, NonClassCopyMode copyMode, TinyRemapper remapper) throws IOException { + this.addNonClassFiles(srcFile, remapper, copyMode.remappers); + } + + public void addNonClassFiles(Path srcFile, TinyRemapper remapper, List remappers) throws IOException { if (Files.isDirectory(srcFile)) { - addNonClassFiles(srcFile, copyMode, remapper, false); + addNonClassFiles(srcFile, remapper, false, remappers); } else if (Files.exists(srcFile)) { if (!srcFile.getFileName().toString().endsWith(classSuffix)) { - addNonClassFiles(FileSystems.newFileSystem(srcFile, null).getPath("/"), copyMode, remapper, true); + addNonClassFiles(FileSystems.newFileSystem(srcFile, null).getPath("/"), remapper, true, remappers); } } else { throw new FileNotFoundException("file "+srcFile+" doesn't exist"); } } + public void addNonClassFiles(Path srcDir, NonClassCopyMode copyMode, TinyRemapper remapper, boolean closeFs) throws IOException { + this.addNonClassFiles(srcDir, remapper, closeFs, copyMode.remappers); + } + + public void addNonClassFiles(Path srcDir, TinyRemapper remapper, boolean closeFs, List resourceRemappers) throws IOException { try { if (lock != null) lock.lock(); if (closed) throw new IllegalStateException("consumer already closed"); @@ -152,42 +155,19 @@ public void addNonClassFiles(Path srcDir, NonClassCopyMode copyMode, TinyRemappe @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String fileName = file.getFileName().toString(); - if (!fileName.endsWith(classSuffix)) { Path relativePath = srcDir.relativize(file); Path dstFile = dstDir.resolve(relativePath.toString()); // toString bypasses resolve requiring identical fs providers - - if (copyMode == NonClassCopyMode.UNCHANGED - || !relativePath.startsWith("META-INF") - || copyMode == NonClassCopyMode.SKIP_META_INF && relativePath.getNameCount() != 2) { // allow sub-folders of META-INF - createParentDirs(dstFile); - Files.copy(file, dstFile, StandardCopyOption.REPLACE_EXISTING); - } else if (copyMode == NonClassCopyMode.FIX_META_INF && !shouldStripForFixMeta(relativePath)) { - createParentDirs(dstFile); - - if (fileName.equals("MANIFEST.MF")) { - Manifest manifest; - - try (InputStream is = Files.newInputStream(file)) { - manifest = new Manifest(is); - } - - fixManifest(manifest, remapper); - - try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(dstFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE))) { - manifest.write(os); - } - } else if (remapper != null && relativePath.getNameCount() == 3 && relativePath.getName(1).toString().equals("services")) { - fileName = mapFullyQualifiedClassName(fileName, remapper); - - try (BufferedReader reader = Files.newBufferedReader(file); - BufferedWriter writer = Files.newBufferedWriter(dstFile.getParent().resolve(fileName), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { - fixServiceDecl(reader, writer, remapper); + for (ResourceRemapper resourceRemapper : resourceRemappers) { + if(resourceRemapper.canTransform(remapper, relativePath)) { + try(InputStream input = new BufferedInputStream(Files.newInputStream(file))) { + resourceRemapper.transform(dstDir, relativePath, input, remapper); + return FileVisitResult.CONTINUE; } - } else { - Files.copy(file, dstFile, StandardCopyOption.REPLACE_EXISTING); } } + createParentDirs(dstFile); + Files.copy(file, dstFile, StandardCopyOption.REPLACE_EXISTING); } return FileVisitResult.CONTINUE; @@ -200,87 +180,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } } - private static boolean shouldStripForFixMeta(Path file) { - if (file.getNameCount() != 2) return false; // not directly inside META-INF dir - - assert file.getName(0).toString().equals("META-INF"); - - String fileName = file.getFileName().toString(); - - // https://docs.oracle.com/en/java/javase/12/docs/specs/jar/jar.html#signed-jar-file - return fileName.endsWith(".SF") - || fileName.endsWith(".DSA") - || fileName.endsWith(".RSA") - || fileName.startsWith("SIG-"); - } - - private static String mapFullyQualifiedClassName(String name, TinyRemapper remapper) { - assert name.indexOf('/') < 0; - - return remapper.mapClass(name.replace('.', '/')).replace('/', '.'); - } - - private static void fixManifest(Manifest manifest, TinyRemapper remapper) { - Attributes mainAttrs = manifest.getMainAttributes(); - - if (remapper != null) { - String val = mainAttrs.getValue(Attributes.Name.MAIN_CLASS); - if (val != null) mainAttrs.put(Attributes.Name.MAIN_CLASS, mapFullyQualifiedClassName(val, remapper)); - - val = mainAttrs.getValue("Launcher-Agent-Class"); - if (val != null) mainAttrs.put("Launcher-Agent-Class", mapFullyQualifiedClassName(val, remapper)); - } - - mainAttrs.remove(Attributes.Name.SIGNATURE_VERSION); - - for (Iterator it = manifest.getEntries().values().iterator(); it.hasNext(); ) { - Attributes attrs = it.next(); - - for (Iterator it2 = attrs.keySet().iterator(); it2.hasNext(); ) { - Attributes.Name attrName = (Attributes.Name) it2.next(); - String name = attrName.toString(); - - if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) { - it2.remove(); - } - } - - if (attrs.isEmpty()) it.remove(); - } - } - - private static void fixServiceDecl(BufferedReader reader, BufferedWriter writer, TinyRemapper remapper) throws IOException { - String line; - - while ((line = reader.readLine()) != null) { - int end = line.indexOf('#'); - if (end < 0) end = line.length(); - - // trim start+end to skip ' ' and '\t' - - int start = 0; - char c; - - while (start < end && ((c = line.charAt(start)) == ' ' || c == '\t')) { - start++; - } - - while (end > start && ((c = line.charAt(end - 1)) == ' ' || c == '\t')) { - end--; - } - - if (start == end) { - writer.write(line); - } else { - writer.write(line, 0, start); - writer.write(mapFullyQualifiedClassName(line.substring(start, end), remapper)); - writer.write(line, end, line.length() - end); - } - - writer.newLine(); - } - } - @Override public void accept(String clsName, byte[] data) { if (classNameFilter != null && !classNameFilter.test(clsName)) return; @@ -346,4 +245,10 @@ private static void createParentDirs(Path path) throws IOException { private final Lock lock; private final Predicate classNameFilter; private boolean closed; + + public interface ResourceRemapper { + boolean canTransform(TinyRemapper remapper, Path relativePath); + + void transform(Path destinationDirectory, Path relativePath, InputStream input, TinyRemapper remapper) throws IOException; + } }