mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Move jar merger to loom. (#882)
* Move jar merger to loom. * Fix copyright years
This commit is contained in:
@@ -32,7 +32,6 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.fabricmc.loom.configuration.ConfigContext;
|
||||
import net.fabricmc.stitch.merge.JarMerger;
|
||||
|
||||
public final class MergedMinecraftProvider extends MinecraftProvider {
|
||||
private Path minecraftMergedJar;
|
||||
@@ -86,7 +85,7 @@ public final class MergedMinecraftProvider extends MinecraftProvider {
|
||||
|
||||
Objects.requireNonNull(jarToMerge, "Cannot merge null input jar?");
|
||||
|
||||
try (JarMerger jarMerger = new JarMerger(getMinecraftClientJar(), jarToMerge, minecraftMergedJar.toFile())) {
|
||||
try (var jarMerger = new MinecraftJarMerger(getMinecraftClientJar(), jarToMerge, minecraftMergedJar.toFile())) {
|
||||
jarMerger.enableSyntheticParamsOffset();
|
||||
jarMerger.merge();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2023 FabricMC
|
||||
*
|
||||
* 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 net.fabricmc.loom.configuration.providers.minecraft;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.InnerClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
|
||||
public class MinecraftClassMerger {
|
||||
private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;";
|
||||
private static final String ITF_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterface;";
|
||||
private static final String ITF_LIST_DESCRIPTOR = "Lnet/fabricmc/api/EnvironmentInterfaces;";
|
||||
private static final String SIDED_DESCRIPTOR = "Lnet/fabricmc/api/Environment;";
|
||||
|
||||
private abstract static class Merger<T> {
|
||||
private final Map<String, T> entriesClient, entriesServer;
|
||||
private final List<String> entryNames;
|
||||
|
||||
Merger(List<T> entriesClient, List<T> entriesServer) {
|
||||
this.entriesClient = new LinkedHashMap<>();
|
||||
this.entriesServer = new LinkedHashMap<>();
|
||||
|
||||
List<String> listClient = toMap(entriesClient, this.entriesClient);
|
||||
List<String> listServer = toMap(entriesServer, this.entriesServer);
|
||||
|
||||
this.entryNames = mergePreserveOrder(listClient, listServer);
|
||||
}
|
||||
|
||||
public abstract String getName(T entry);
|
||||
|
||||
public abstract void applySide(T entry, String side);
|
||||
|
||||
private List<String> toMap(List<T> entries, Map<String, T> map) {
|
||||
List<String> list = new ArrayList<>(entries.size());
|
||||
|
||||
for (T entry : entries) {
|
||||
String name = getName(entry);
|
||||
map.put(name, entry);
|
||||
list.add(name);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void merge(List<T> list) {
|
||||
for (String s : entryNames) {
|
||||
T entryClient = entriesClient.get(s);
|
||||
T entryServer = entriesServer.get(s);
|
||||
|
||||
if (entryClient != null && entryServer != null) {
|
||||
list.add(entryClient);
|
||||
} else if (entryClient != null) {
|
||||
applySide(entryClient, "CLIENT");
|
||||
list.add(entryClient);
|
||||
} else {
|
||||
applySide(entryServer, "SERVER");
|
||||
list.add(entryServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void visitSideAnnotation(AnnotationVisitor av, String side) {
|
||||
av.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT));
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
private static void visitItfAnnotation(AnnotationVisitor av, String side, List<String> itfDescriptors) {
|
||||
for (String itf : itfDescriptors) {
|
||||
AnnotationVisitor avItf = av.visitAnnotation(null, ITF_DESCRIPTOR);
|
||||
avItf.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT));
|
||||
avItf.visit("itf", Type.getType("L" + itf + ";"));
|
||||
avItf.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SidedClassVisitor extends ClassVisitor {
|
||||
private final String side;
|
||||
|
||||
public SidedClassVisitor(int api, ClassVisitor cv, String side) {
|
||||
super(api, cv);
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
AnnotationVisitor av = cv.visitAnnotation(SIDED_DESCRIPTOR, true);
|
||||
visitSideAnnotation(av, side);
|
||||
super.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftClassMerger() {
|
||||
}
|
||||
|
||||
public byte[] merge(byte[] classClient, byte[] classServer) {
|
||||
ClassReader readerC = new ClassReader(classClient);
|
||||
ClassReader readerS = new ClassReader(classServer);
|
||||
ClassWriter writer = new ClassWriter(0);
|
||||
|
||||
ClassNode nodeC = new ClassNode(Constants.ASM_VERSION);
|
||||
readerC.accept(nodeC, 0);
|
||||
|
||||
ClassNode nodeS = new ClassNode(Constants.ASM_VERSION);
|
||||
readerS.accept(nodeS, 0);
|
||||
|
||||
ClassNode nodeOut = new ClassNode(Constants.ASM_VERSION);
|
||||
nodeOut.version = nodeC.version;
|
||||
nodeOut.access = nodeC.access;
|
||||
nodeOut.name = nodeC.name;
|
||||
nodeOut.signature = nodeC.signature;
|
||||
nodeOut.superName = nodeC.superName;
|
||||
nodeOut.sourceFile = nodeC.sourceFile;
|
||||
nodeOut.sourceDebug = nodeC.sourceDebug;
|
||||
nodeOut.outerClass = nodeC.outerClass;
|
||||
nodeOut.outerMethod = nodeC.outerMethod;
|
||||
nodeOut.outerMethodDesc = nodeC.outerMethodDesc;
|
||||
nodeOut.module = nodeC.module;
|
||||
nodeOut.nestHostClass = nodeC.nestHostClass;
|
||||
nodeOut.nestMembers = nodeC.nestMembers;
|
||||
nodeOut.attrs = nodeC.attrs;
|
||||
|
||||
if (nodeC.invisibleAnnotations != null) {
|
||||
nodeOut.invisibleAnnotations = new ArrayList<>();
|
||||
nodeOut.invisibleAnnotations.addAll(nodeC.invisibleAnnotations);
|
||||
}
|
||||
|
||||
if (nodeC.invisibleTypeAnnotations != null) {
|
||||
nodeOut.invisibleTypeAnnotations = new ArrayList<>();
|
||||
nodeOut.invisibleTypeAnnotations.addAll(nodeC.invisibleTypeAnnotations);
|
||||
}
|
||||
|
||||
if (nodeC.visibleAnnotations != null) {
|
||||
nodeOut.visibleAnnotations = new ArrayList<>();
|
||||
nodeOut.visibleAnnotations.addAll(nodeC.visibleAnnotations);
|
||||
}
|
||||
|
||||
if (nodeC.visibleTypeAnnotations != null) {
|
||||
nodeOut.visibleTypeAnnotations = new ArrayList<>();
|
||||
nodeOut.visibleTypeAnnotations.addAll(nodeC.visibleTypeAnnotations);
|
||||
}
|
||||
|
||||
List<String> itfs = mergePreserveOrder(nodeC.interfaces, nodeS.interfaces);
|
||||
nodeOut.interfaces = new ArrayList<>();
|
||||
|
||||
List<String> clientItfs = new ArrayList<>();
|
||||
List<String> serverItfs = new ArrayList<>();
|
||||
|
||||
for (String s : itfs) {
|
||||
boolean nc = nodeC.interfaces.contains(s);
|
||||
boolean ns = nodeS.interfaces.contains(s);
|
||||
nodeOut.interfaces.add(s);
|
||||
|
||||
if (nc && !ns) {
|
||||
clientItfs.add(s);
|
||||
} else if (ns && !nc) {
|
||||
serverItfs.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientItfs.isEmpty() || !serverItfs.isEmpty()) {
|
||||
AnnotationVisitor envInterfaces = nodeOut.visitAnnotation(ITF_LIST_DESCRIPTOR, false);
|
||||
AnnotationVisitor eiArray = envInterfaces.visitArray("value");
|
||||
|
||||
if (!clientItfs.isEmpty()) {
|
||||
visitItfAnnotation(eiArray, "CLIENT", clientItfs);
|
||||
}
|
||||
|
||||
if (!serverItfs.isEmpty()) {
|
||||
visitItfAnnotation(eiArray, "SERVER", serverItfs);
|
||||
}
|
||||
|
||||
eiArray.visitEnd();
|
||||
envInterfaces.visitEnd();
|
||||
}
|
||||
|
||||
new Merger<>(nodeC.innerClasses, nodeS.innerClasses) {
|
||||
@Override
|
||||
public String getName(InnerClassNode entry) {
|
||||
return entry.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySide(InnerClassNode entry, String side) {
|
||||
}
|
||||
}.merge(nodeOut.innerClasses);
|
||||
|
||||
new Merger<>(nodeC.fields, nodeS.fields) {
|
||||
@Override
|
||||
public String getName(FieldNode entry) {
|
||||
return entry.name + ";;" + entry.desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySide(FieldNode entry, String side) {
|
||||
AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false);
|
||||
visitSideAnnotation(av, side);
|
||||
}
|
||||
}.merge(nodeOut.fields);
|
||||
|
||||
new Merger<>(nodeC.methods, nodeS.methods) {
|
||||
@Override
|
||||
public String getName(MethodNode entry) {
|
||||
return entry.name + entry.desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySide(MethodNode entry, String side) {
|
||||
AnnotationVisitor av = entry.visitAnnotation(SIDED_DESCRIPTOR, false);
|
||||
visitSideAnnotation(av, side);
|
||||
}
|
||||
}.merge(nodeOut.methods);
|
||||
|
||||
nodeOut.accept(writer);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
|
||||
private static List<String> mergePreserveOrder(List<String> first, List<String> second) {
|
||||
List<String> out = new ArrayList<>();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
while (i < first.size() || j < second.size()) {
|
||||
while (i < first.size() && j < second.size()
|
||||
&& first.get(i).equals(second.get(j))) {
|
||||
out.add(first.get(i));
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
while (i < first.size() && !second.contains(first.get(i))) {
|
||||
out.add(first.get(i));
|
||||
i++;
|
||||
}
|
||||
|
||||
while (j < second.size() && !first.contains(second.get(j))) {
|
||||
out.add(second.get(j));
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2023 FabricMC
|
||||
*
|
||||
* 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 net.fabricmc.loom.configuration.providers.minecraft;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.SnowmanClassVisitor;
|
||||
import net.fabricmc.loom.util.SyntheticParameterClassVisitor;
|
||||
|
||||
public class MinecraftJarMerger implements AutoCloseable {
|
||||
public static class Entry {
|
||||
public final Path path;
|
||||
public final BasicFileAttributes metadata;
|
||||
public final byte[] data;
|
||||
|
||||
public Entry(Path path, BasicFileAttributes metadata, byte[] data) {
|
||||
this.path = path;
|
||||
this.metadata = metadata;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private static final MinecraftClassMerger CLASS_MERGER = new MinecraftClassMerger();
|
||||
private final FileSystemUtil.Delegate inputClientFs, inputServerFs, outputFs;
|
||||
private final Path inputClient, inputServer;
|
||||
private final Map<String, Entry> entriesClient, entriesServer;
|
||||
private final Set<String> entriesAll;
|
||||
private boolean removeSnowmen = false;
|
||||
private boolean offsetSyntheticsParams = false;
|
||||
|
||||
public MinecraftJarMerger(File inputClient, File inputServer, File output) throws IOException {
|
||||
if (output.exists()) {
|
||||
if (!output.delete()) {
|
||||
throw new IOException("Could not delete " + output.getName());
|
||||
}
|
||||
}
|
||||
|
||||
this.inputClient = (inputClientFs = FileSystemUtil.getJarFileSystem(inputClient, false)).get().getPath("/");
|
||||
this.inputServer = (inputServerFs = FileSystemUtil.getJarFileSystem(inputServer, false)).get().getPath("/");
|
||||
this.outputFs = FileSystemUtil.getJarFileSystem(output, true);
|
||||
|
||||
this.entriesClient = new HashMap<>();
|
||||
this.entriesServer = new HashMap<>();
|
||||
this.entriesAll = new TreeSet<>();
|
||||
}
|
||||
|
||||
public void enableSnowmanRemoval() {
|
||||
removeSnowmen = true;
|
||||
}
|
||||
|
||||
public void enableSyntheticParamsOffset() {
|
||||
offsetSyntheticsParams = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputClientFs.close();
|
||||
inputServerFs.close();
|
||||
outputFs.close();
|
||||
}
|
||||
|
||||
private void readToMap(Map<String, Entry> map, Path input) {
|
||||
try {
|
||||
Files.walkFileTree(input, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException {
|
||||
if (attr.isDirectory()) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (!path.getFileName().toString().endsWith(".class")) {
|
||||
if (path.toString().equals("/META-INF/MANIFEST.MF")) {
|
||||
map.put("META-INF/MANIFEST.MF", new Entry(path, attr,
|
||||
"Manifest-Version: 1.0\nMain-Class: net.minecraft.client.Main\n".getBytes(StandardCharsets.UTF_8)));
|
||||
} else {
|
||||
if (path.toString().startsWith("/META-INF/")) {
|
||||
if (path.toString().endsWith(".SF") || path.toString().endsWith(".RSA")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
map.put(path.toString().substring(1), new Entry(path, attr, null));
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
byte[] output = Files.readAllBytes(path);
|
||||
map.put(path.toString().substring(1), new Entry(path, attr, output));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void add(Entry entry) throws IOException {
|
||||
Path outPath = outputFs.get().getPath(entry.path.toString());
|
||||
|
||||
if (outPath.getParent() != null) {
|
||||
Files.createDirectories(outPath.getParent());
|
||||
}
|
||||
|
||||
if (entry.data != null) {
|
||||
Files.write(outPath, entry.data, StandardOpenOption.CREATE_NEW);
|
||||
} else {
|
||||
Files.copy(entry.path, outPath);
|
||||
}
|
||||
|
||||
Files.getFileAttributeView(outPath, BasicFileAttributeView.class)
|
||||
.setTimes(
|
||||
entry.metadata.creationTime(),
|
||||
entry.metadata.lastAccessTime(),
|
||||
entry.metadata.lastModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
public void merge() throws IOException {
|
||||
ExecutorService service = Executors.newFixedThreadPool(2);
|
||||
service.submit(() -> readToMap(entriesClient, inputClient));
|
||||
service.submit(() -> readToMap(entriesServer, inputServer));
|
||||
service.shutdown();
|
||||
|
||||
try {
|
||||
service.awaitTermination(1, TimeUnit.HOURS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
entriesAll.addAll(entriesClient.keySet());
|
||||
entriesAll.addAll(entriesServer.keySet());
|
||||
|
||||
List<Entry> entries = entriesAll.parallelStream().map((entry) -> {
|
||||
boolean isClass = entry.endsWith(".class");
|
||||
boolean isMinecraft = entriesClient.containsKey(entry) || entry.startsWith("net/minecraft") || !entry.contains("/");
|
||||
Entry result;
|
||||
String side = null;
|
||||
|
||||
Entry entry1 = entriesClient.get(entry);
|
||||
Entry entry2 = entriesServer.get(entry);
|
||||
|
||||
if (entry1 != null && entry2 != null) {
|
||||
if (Arrays.equals(entry1.data, entry2.data)) {
|
||||
result = entry1;
|
||||
} else {
|
||||
if (isClass) {
|
||||
result = new Entry(entry1.path, entry1.metadata, CLASS_MERGER.merge(entry1.data, entry2.data));
|
||||
} else {
|
||||
// FIXME: More heuristics?
|
||||
result = entry1;
|
||||
}
|
||||
}
|
||||
} else if ((result = entry1) != null) {
|
||||
side = "CLIENT";
|
||||
} else if ((result = entry2) != null) {
|
||||
side = "SERVER";
|
||||
}
|
||||
|
||||
if (isClass && !isMinecraft && "SERVER".equals(side)) {
|
||||
// Server bundles libraries, client doesn't - skip them
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
if (isMinecraft && isClass) {
|
||||
byte[] data = result.data;
|
||||
ClassReader reader = new ClassReader(data);
|
||||
ClassWriter writer = new ClassWriter(0);
|
||||
ClassVisitor visitor = writer;
|
||||
|
||||
if (side != null) {
|
||||
visitor = new MinecraftClassMerger.SidedClassVisitor(Constants.ASM_VERSION, visitor, side);
|
||||
}
|
||||
|
||||
if (removeSnowmen) {
|
||||
visitor = new SnowmanClassVisitor(Constants.ASM_VERSION, visitor);
|
||||
}
|
||||
|
||||
if (offsetSyntheticsParams) {
|
||||
visitor = new SyntheticParameterClassVisitor(Constants.ASM_VERSION, visitor);
|
||||
}
|
||||
|
||||
if (visitor != writer) {
|
||||
reader.accept(visitor, 0);
|
||||
data = writer.toByteArray();
|
||||
result = new Entry(result.path, result.metadata, data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull).toList();
|
||||
|
||||
for (Entry e : entries) {
|
||||
add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2023 FabricMC
|
||||
*
|
||||
* 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 net.fabricmc.loom.util;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
public class SnowmanClassVisitor extends ClassVisitor {
|
||||
public static class SnowmanMethodVisitor extends MethodVisitor {
|
||||
public SnowmanMethodVisitor(int api, MethodVisitor methodVisitor) {
|
||||
super(api, methodVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParameter(final String name, final int access) {
|
||||
if (name != null && name.startsWith("\u2603")) {
|
||||
super.visitParameter(null, access);
|
||||
} else {
|
||||
super.visitParameter(name, access);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(
|
||||
final String name,
|
||||
final String descriptor,
|
||||
final String signature,
|
||||
final Label start,
|
||||
final Label end,
|
||||
final int index) {
|
||||
String newName = name;
|
||||
|
||||
if (name != null && name.startsWith("\u2603")) {
|
||||
newName = "lvt" + index;
|
||||
}
|
||||
|
||||
super.visitLocalVariable(newName, descriptor, signature, start, end, index);
|
||||
}
|
||||
}
|
||||
|
||||
public SnowmanClassVisitor(int api, ClassVisitor cv) {
|
||||
super(api, cv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSource(final String source, final String debug) {
|
||||
// Don't trust the obfuscation on this.
|
||||
super.visitSource(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(
|
||||
final int access,
|
||||
final String name,
|
||||
final String descriptor,
|
||||
final String signature,
|
||||
final String[] exceptions) {
|
||||
return new SnowmanMethodVisitor(api, super.visitMethod(access, name, descriptor, signature, exceptions));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2016-2023 FabricMC
|
||||
*
|
||||
* 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 net.fabricmc.loom.util;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* ProGuard has a bug where parameter annotations are applied incorrectly in the presence of
|
||||
* synthetic arguments. This causes javac to balk when trying to load affected classes.
|
||||
*
|
||||
* <p>We use several heuristics to guess what the synthetic arguments may be for a particular
|
||||
* constructor. We then check if the constructor matches our guess, and if so, offset all
|
||||
* parameter annotations.
|
||||
*/
|
||||
public class SyntheticParameterClassVisitor extends ClassVisitor {
|
||||
private static class SyntheticMethodVisitor extends MethodVisitor {
|
||||
private final int offset;
|
||||
|
||||
SyntheticMethodVisitor(int api, int offset, MethodVisitor methodVisitor) {
|
||||
super(api, methodVisitor);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {
|
||||
return super.visitParameterAnnotation(parameter - offset, descriptor, visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
|
||||
super.visitAnnotableParameterCount(parameterCount - offset, visible);
|
||||
}
|
||||
}
|
||||
|
||||
private String className;
|
||||
private int synthetic;
|
||||
private String syntheticArgs;
|
||||
private boolean backoff = false;
|
||||
|
||||
public SyntheticParameterClassVisitor(int api, ClassVisitor cv) {
|
||||
super(api, cv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
|
||||
this.className = name;
|
||||
|
||||
// Enums will always have a string name and then the ordinal
|
||||
if ((access & Opcodes.ACC_ENUM) != 0) {
|
||||
synthetic = 2;
|
||||
syntheticArgs = "(Ljava/lang/String;I";
|
||||
}
|
||||
|
||||
if (version >= 55) {
|
||||
// Backoff on java 11 or newer due to nest mates being used.
|
||||
backoff = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
super.visitInnerClass(name, outerName, innerName, access);
|
||||
|
||||
// If we're a non-static, non-anonymous inner class then we can assume the first argument
|
||||
// is the parent class.
|
||||
// See https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.8.1
|
||||
if (synthetic == 0 && name.equals(this.className) && innerName != null && outerName != null && (access & Opcodes.ACC_STATIC) == 0) {
|
||||
this.synthetic = 1;
|
||||
this.syntheticArgs = "(L" + outerName + ";";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(
|
||||
final int access,
|
||||
final String name,
|
||||
final String descriptor,
|
||||
final String signature,
|
||||
final String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
|
||||
return mv != null && synthetic != 0 && name.equals("<init>") && descriptor.startsWith(syntheticArgs) && !backoff
|
||||
? new SyntheticMethodVisitor(api, synthetic, mv)
|
||||
: mv;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user