package decompsource; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FileReader; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import bytecode.BaseStreamingZipProcessor; public class RemapSources { private Map<String, String> fieldNames = new HashMap<>(); private Map<String, String> fieldDocs = new HashMap<>(); private Map<String, String> methodNames = new HashMap<>(); private Map<String, String> methodDocs = new HashMap<>(); private Map<String, String> paramNames = new HashMap<>(); public boolean noJavadocs; private boolean doesJavadocs = false; // currently unsettable; TODO what's the relationship between this and noJavadocs? public void readConfigs(String methodsFile, String fieldsFile, String paramsFile) throws Exception { try (BufferedReader reader = new BufferedReader(new FileReader(methodsFile))) { String line; while((line = reader.readLine()) != null) { String[] parts = line.split(",",-1); methodNames.put(parts[0], parts[1]); methodDocs.put(parts[0], parts[3]); } } try (BufferedReader reader = new BufferedReader(new FileReader(fieldsFile))) { String line; while((line = reader.readLine()) != null) { String[] parts = line.split(",",-1); fieldNames.put(parts[0], parts[1]); fieldDocs.put(parts[0], parts[3]); } } try (BufferedReader reader = new BufferedReader(new FileReader(paramsFile))) { String line; while((line = reader.readLine()) != null) { String[] parts = line.split(",",-1); paramNames.put(parts[0], parts[1]); } } } public static void main(String[] args) { if(args.length != 4) { System.err.println("Usage: java RemapSources methods.csv fields.csv params.csv true/false < infile.jar > outfile.jar"); System.exit(1); } try { RemapSources r = new RemapSources(); r.readConfigs(args[0], args[1], args[2]); r.noJavadocs = Boolean.parseBoolean(args[3]); r.go(System.in, System.out); } catch(Throwable t) { t.printStackTrace(); System.exit(1); } System.exit(0); } public void go(InputStream in, OutputStream out) throws Exception { try (ZipInputStream zipIn = new ZipInputStream(in)) { try (ZipOutputStream zipOut = new ZipOutputStream(out)) { ZipEntry ze; while((ze = zipIn.getNextEntry()) != null) { zipOut.putNextEntry(new ZipEntry(ze.getName())); if(!ze.getName().endsWith(".java")) { BaseStreamingZipProcessor.copyResource(zipIn, zipOut); } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BaseStreamingZipProcessor.copyResource(zipIn, baos); byte[] bytes = baos.toByteArray(); bytes = process(bytes, ze.getName()); zipOut.write(bytes); } zipIn.closeEntry(); zipOut.closeEntry(); } } } } private static final Pattern SRG_FINDER = Pattern.compile("(func_[0-9]+_[a-zA-Z_]+|field_[0-9]+_[a-zA-Z_]+|p_[\\w]+_\\d+_)([^\\w\\$])"); private static final Pattern METHOD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(func_[0-9]+_[a-zA-Z_]+)\\("); private static final Pattern FIELD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(field_[0-9]+_[a-zA-Z_]+) *(?:=|;)"); private byte[] process(byte[] bytes, String filename) { Matcher matcher; ArrayList<String> newLines = new ArrayList<String>(); for (String line : new String(bytes, StandardCharsets.UTF_8).split("\n")) { if (noJavadocs) // noajavadocs? dont bothe with the rest of this crap... { newLines.add(replaceInLine(line)); continue; } matcher = METHOD.matcher(line); if (matcher.find()) { String name = matcher.group(2); if (methodNames.containsKey(name)) { String javadoc = methodDocs.get(name); if(javadoc != null && !javadoc.isEmpty()) { if (doesJavadocs) javadoc = buildJavadoc(matcher.group(1), javadoc, true); else javadoc = matcher.group(1) + "// JAVADOC METHOD $$ " + name; insetAboveAnnotations(newLines, javadoc); } } } else if (line.trim().startsWith("// JAVADOC ")) { Matcher match = SRG_FINDER.matcher(line); if (match.find()) { String indent = line.substring(0, line.indexOf("// JAVADOC")); String name = match.group(); if (name.startsWith("func_")) { String mtdDoc = methodDocs.get(name); if(mtdDoc != null && !mtdDoc.isEmpty()) { line = buildJavadoc(indent, mtdDoc, true); } } else if (name.startsWith("field_")) { String fldDoc = fieldDocs.get(name); if(fldDoc != null && !fldDoc.isEmpty()) { line = buildJavadoc(indent, fldDoc, true); } } if (line.endsWith("\n")) { line = line.substring(0, line.length() - "\n".length()); } } } else { matcher = FIELD.matcher(line); if (matcher.find()) { String name = matcher.group(2); if (fieldNames.containsKey(name)) { String javadoc = fieldDocs.get(name); if(javadoc != null && !javadoc.isEmpty()) { if (doesJavadocs) javadoc = buildJavadoc(matcher.group(1), javadoc, false); else javadoc = matcher.group(1) + "// JAVADOC FIELD $$ " + name; insetAboveAnnotations(newLines, javadoc); } } } } newLines.add(replaceInLine(line)); } StringBuilder sb = new StringBuilder(); for(String line : newLines) { if(sb.length() != 0) sb.append("\n"); sb.append(line); } return sb.toString().getBytes(StandardCharsets.UTF_8); } private static void insetAboveAnnotations(List<String> list, String line) { int back = 0; while (list.get(list.size() - 1 - back).trim().startsWith("@")) { back++; } list.add(list.size() - back, line); } private String replaceInLine(String line) { // FAR all methods StringBuffer buf = new StringBuffer(); Matcher matcher = SRG_FINDER.matcher(line); while (matcher.find()) { String find = matcher.group(1); if (find.startsWith("p_")) find = paramNames.get(find); else if (find.startsWith("func_")) find = methodNames.get(find); else if (find.startsWith("field_")) find = fieldNames.get(find); if (find == null) find = matcher.group(1); matcher.appendReplacement(buf, find); buf.append(matcher.group(2)); } matcher.appendTail(buf); return buf.toString(); } private static String buildJavadoc(String indent, String javadoc, boolean isMethod) { StringBuilder builder = new StringBuilder(); if (javadoc.length() >= 70 || isMethod) { List<String> list = wrapText(javadoc, 120 - (indent.length() + 3)); builder.append(indent); builder.append("/**\n"); for (String line : list) { builder.append(indent); builder.append(" * "); builder.append(line); builder.append('\n'); } builder.append(indent); builder.append(" */"); //builder.append(Constants.NEWLINE); } // one line else { builder.append(indent); builder.append("/** "); builder.append(javadoc); builder.append(" */"); //builder.append(Constants.NEWLINE); } return builder.toString().replace(indent, indent); } private static List<String> wrapText(String text, int len) { // return empty array for null text if (text == null) { return new ArrayList<String>(); } // return text if len is zero or less if (len <= 0) { return new ArrayList<String>(Arrays.asList(text)); } // return text if less than length if (text.length() <= len) { return new ArrayList<String>(Arrays.asList(text)); } List<String> lines = new ArrayList<String>(); StringBuilder line = new StringBuilder(); StringBuilder word = new StringBuilder(); int tempNum; // each char in array for (char c : text.toCharArray()) { // its a wordBreaking character. if (c == ' ' || c == ',' || c == '-') { // add the character to the word word.append(c); // its a space. set TempNum to 1, otherwise leave it as a wrappable char tempNum = Character.isWhitespace(c) ? 1 : 0; // subtract tempNum from the length of the word if ((line.length() + word.length() - tempNum) > len) { lines.add(line.toString()); line.delete(0, line.length()); } // new word, add it to the next line and clear the word line.append(word); word.delete(0, word.length()); } // not a linebreak char else { // add it to the word and move on word.append(c); } } // handle any extra chars in current word if (word.length() > 0) { if ((line.length() + word.length()) > len) { lines.add(line.toString()); line.delete(0, line.length()); } line.append(word); } // handle extra line if (line.length() > 0) { lines.add(line.toString()); } List<String> temp = new ArrayList<String>(lines.size()); for (String s : lines) { temp.add(s.trim()); } return temp; } }