package net.fybertech.dynamicmappings; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; public class MergeJars { public static Map<String, ClassInfo> classes = new HashMap<String, ClassInfo>(); public static Map<String, MiscInfo> miscFiles = new HashMap<String, MiscInfo>(); public static boolean quiet = false; public static final int CLIENT = 1; public static final int SERVER = 2; public static final int BOTH = 3; public static class MiscInfo { String name; int onClientServer = 0; } public static class ClassInfo { String name; Map<String, FieldInfo> fields = new HashMap<String, FieldInfo>(); Map<String, MethodInfo> methods = new HashMap<String, MethodInfo>(); int onClientServer = 0; } public static class FieldInfo { String name; String desc; int onClientServer = 0; } public static class MethodInfo { String name; String desc; int onClientServer = 0; } public static String[] excluded = new String[] { "META-INF/", "org/", "com/", "gnu/", "io/", "javax/", "org/" }; public static JarFile discoverClasses(File filename, int clientOrServer) { JarFile jarFile = null; try { jarFile = new JarFile(filename); } catch (IOException e) { e.printStackTrace(); } Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); boolean isExcluded = false; for (String s : excluded) if (entryName.startsWith(s)) { isExcluded = true; break; } if (isExcluded) continue; if (!entryName.endsWith(".class")) { MiscInfo info = miscFiles.get(entryName); if (info == null) { info = new MiscInfo(); info.name = entryName; miscFiles.put(entryName, info); } info.onClientServer |= clientOrServer; continue; } byte[] buffer = getFileFromJar(entry, jarFile); ClassReader reader = new ClassReader(buffer); ClassNode cn = new ClassNode(); reader.accept(cn, 0); ClassInfo info = classes.get(cn.name); if (info == null) { info = new ClassInfo(); classes.put(cn.name, info); } info.name = cn.name; info.onClientServer |= clientOrServer; for (FieldNode field : (List<FieldNode>)cn.fields) { FieldInfo finfo = info.fields.get(field.name); if (finfo == null) { finfo = new FieldInfo(); finfo.name = field.name; finfo.desc = field.desc; info.fields.put(field.name, finfo); } finfo.onClientServer |= clientOrServer; } for (MethodNode method : (List<MethodNode>)cn.methods) { MethodInfo minfo = info.methods.get(method.name + method.desc); if (minfo == null) { minfo = new MethodInfo(); minfo.name = method.name; minfo.desc = method.desc; info.methods.put(method.name + method.desc, minfo); } minfo.onClientServer |= clientOrServer; } } return jarFile; } public static byte[] getFileFromJar(ZipEntry entry, JarFile jarFile) { byte[] buffer = null; if (entry != null) { try { InputStream stream = jarFile.getInputStream(entry); int pos = 0; buffer = new byte[(int)entry.getSize()]; while (true) { int read = stream.read(buffer, pos, Math.min(1024, (int)entry.getSize() - pos)); pos += read; if (read < 1) break; } } catch (IOException e) { e.printStackTrace(); } } return buffer; } public static byte[] getFileFromJar(String filename, JarFile jarFile) { return getFileFromJar(jarFile.getEntry(filename), jarFile); } public static byte[] getFileFromJar(String filename, String jarName) { byte[] buffer = null; JarFile jarFile = null; try { jarFile = new JarFile(jarName); } catch (IOException e) { e.printStackTrace(); } buffer = getFileFromJar(filename, jarFile); try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } return buffer; } public static ClassNode getClassFromJar(String className, String jarName) { ClassNode node = null; className = className.replace(".", "/"); byte[] buffer = getFileFromJar(className + ".class", jarName); if (buffer != null) { ClassReader reader = new ClassReader(buffer); node = new ClassNode(); reader.accept(node, 0); } return node; } public static ClassNode getClassFromJar(String className, JarFile jarFile) { ClassNode node = null; className = className.replace(".", "/"); byte[] buffer = getFileFromJar(className + ".class", jarFile); if (buffer != null) { ClassReader reader = new ClassReader(buffer); node = new ClassNode(); reader.accept(node, 0); } return node; } public static void writeToFile(String filename, byte[] data) { File f = new File(filename); f.toPath().getParent().toFile().mkdirs(); try { FileOutputStream stream = new FileOutputStream(filename); stream.write(data); stream.close(); } catch (IOException e) { e.printStackTrace(); } } public static void mergeJars(File clientJarFile, File serverJarFile, File outputFile) { if (!quiet) System.out.println("> Parsing client"); JarFile clientJar = discoverClasses(clientJarFile, CLIENT); if (!quiet) System.out.println("> Parsing server"); JarFile serverJar = discoverClasses(serverJarFile, SERVER); JarOutputStream outputJar = null; try { outputJar = new JarOutputStream(new FileOutputStream(outputFile)); } catch (IOException e) { e.printStackTrace(); } if (!quiet) System.out.println("> Merging client and server"); for (MiscInfo info : miscFiles.values()) { if ((info.onClientServer & CLIENT) == CLIENT) { ZipEntry entry = clientJar.getEntry(info.name); try { outputJar.putNextEntry(new ZipEntry(entry)); outputJar.write(getFileFromJar(entry, clientJar)); } catch (IOException e) { e.printStackTrace(); } } else { ZipEntry entry = serverJar.getEntry(info.name); try { outputJar.putNextEntry(new ZipEntry(entry)); outputJar.write(getFileFromJar(entry, serverJar)); } catch (IOException e) { e.printStackTrace(); } } } for (ClassInfo info : classes.values()) { if (info.onClientServer == CLIENT) { ClassNode clientClass = getClassFromJar(info.name, clientJar); if (clientClass.visibleAnnotations == null) clientClass.visibleAnnotations = new ArrayList(); clientClass.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); ClassWriter writer = new ClassWriter(Opcodes.ASM4); clientClass.accept(writer); byte[] classData = writer.toByteArray(); //writeToFile(outputFilename + File.separator + info.name + ".class", classData); try { outputJar.putNextEntry(new ZipEntry(info.name + ".class")); outputJar.write(classData); } catch (IOException e) { e.printStackTrace(); } } else if (info.onClientServer == SERVER) { ClassNode serverClass = getClassFromJar(info.name, serverJar); if (serverClass.visibleAnnotations == null) serverClass.visibleAnnotations = new ArrayList(); serverClass.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); ClassWriter writer = new ClassWriter(Opcodes.ASM4); serverClass.accept(writer); byte[] classData = writer.toByteArray(); //writeToFile(outputFilename + File.separator + info.name + ".class", classData); try { outputJar.putNextEntry(new ZipEntry(info.name + ".class")); outputJar.write(classData); } catch (IOException e) { e.printStackTrace(); } } else { ClassNode clientClass = getClassFromJar(info.name, clientJar); ClassNode serverClass = getClassFromJar(info.name, serverJar); for (FieldInfo field : info.fields.values()) { if (field.onClientServer == SERVER) { FieldNode serverField = null; for (FieldNode fn : (List<FieldNode>)serverClass.fields) { if (fn.name.equals(field.name)) { serverField = fn; break; } } if (serverField.visibleAnnotations == null) serverField.visibleAnnotations = new ArrayList(); serverField.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); clientClass.fields.add(serverField); } else if (field.onClientServer == CLIENT) { FieldNode clientField = null; for (FieldNode fn : (List<FieldNode>)clientClass.fields) { if (fn.name.equals(field.name)) { clientField = fn; break; } } if (clientField.visibleAnnotations == null) clientField.visibleAnnotations = new ArrayList(); clientField.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); } } for (MethodInfo method : info.methods.values()) { if (method.onClientServer == SERVER) { MethodNode serverMethod = null; for (MethodNode fn : (List<MethodNode>)serverClass.methods) { if (fn.name.equals(method.name) && fn.desc.equals(method.desc)) { serverMethod = fn; break; } } if (serverMethod.visibleAnnotations == null) serverMethod.visibleAnnotations = new ArrayList(); serverMethod.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); clientClass.methods.add(serverMethod); } else if (method.onClientServer == CLIENT) { MethodNode clientMethod = null; for (MethodNode fn : (List<MethodNode>)clientClass.methods) { if (fn.name.equals(method.name) && fn.desc.equals(method.desc)) { clientMethod = fn; break; } } if (clientMethod.visibleAnnotations == null) clientMethod.visibleAnnotations = new ArrayList(); clientMethod.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); } } ClassWriter writer = new ClassWriter(Opcodes.ASM4); clientClass.accept(writer); //writeToFile(outputFilename + File.separator + info.name + ".class", writer.toByteArray()); try { outputJar.putNextEntry(new ZipEntry(info.name + ".class")); outputJar.write(writer.toByteArray()); } catch (IOException e) { e.printStackTrace(); } } } try { clientJar.close(); serverJar.close(); outputJar.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { String clientJarFilename = null; String serverJarFilename = null; String outputFilename = null; int nextVal = 0; for (String arg : args) { if (nextVal > 0) { if (nextVal == 1) clientJarFilename = arg; else if (nextVal == 2) serverJarFilename = arg; else outputFilename = arg; nextVal = 0; continue; } if (arg.equalsIgnoreCase("-c")) nextVal = 1; else if (arg.equalsIgnoreCase("-s")) nextVal = 2; else if (arg.equalsIgnoreCase("-o")) nextVal = 3; else if (arg.equalsIgnoreCase("-q")) quiet = true; } if (clientJarFilename == null) { System.err.println("Error: Client jar not specified"); System.exit(1); } if (serverJarFilename == null) { System.err.println("Error: Server jar not specified"); System.exit(2); } if (outputFilename == null) { System.err.println("Error: Output jar not specified"); System.exit(3); } File clientJarFile = new File(clientJarFilename); File serverJarFile = new File(serverJarFilename); if (!clientJarFile.exists()) { System.err.println("Error: Client jar not found"); System.exit(4); } if (!serverJarFile.exists()) { System.err.println("Error: Server jar not found"); System.exit(5); } mergeJars(clientJarFile, serverJarFile, new File(outputFilename)); } }