001: /* ===========================================================================
002: * $RCSfile: ClassDB.java,v $
003: * ===========================================================================
004: *
005: * RetroGuard -- an obfuscation package for Java classfiles.
006: *
007: * Copyright (c) 1998-2006 Mark Welsh (markw@retrologic.com)
008: *
009: * This program can be redistributed and/or modified under the terms of the
010: * Version 2 of the GNU General Public License as published by the Free
011: * Software Foundation.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: */
019:
020: package COM.rl.obf.patch;
021:
022: import java.io.*;
023: import java.util.*;
024: import COM.rl.obf.*;
025:
026: /**
027: * Database of package and class relations formed from an RGS log file.
028: *
029: * @author Mark Welsh
030: */
031: public class ClassDB {
032: // Constants -------------------------------------------------------------
033:
034: // Fields ----------------------------------------------------------------
035: private Pk root = null; // Root package (Java default package)
036:
037: // Class Methods ---------------------------------------------------------
038:
039: // Instance Methods ------------------------------------------------------
040: /** Ctor. */
041: public ClassDB(File rgsFile) throws Exception {
042: try {
043: if (rgsFile != null && rgsFile.exists()) {
044: InputStream rgsInputStream = new FileInputStream(
045: rgsFile);
046: createHierarchy(new RgsEnum(rgsInputStream));
047: rgsInputStream.close();
048: }
049: } catch (Exception e) {
050: // Reset the database
051: root = null;
052: throw new Exception(
053: "Log-file corrupt or in an older format (pre-v1.1)");
054: }
055: }
056:
057: /** Convert entries in file to obfuscated versions. */
058: public Vector toObf(File listFile) throws Exception {
059: Vector outNames = new Vector();
060:
061: // Run through the listed names
062: for (Enumeration enm = getInNames(listFile); enm
063: .hasMoreElements();) {
064: // Convert the obfuscated portions
065: String inName = (String) enm.nextElement();
066: String outName = null;
067:
068: // Ignore inner class listings
069: if (inName.indexOf('$') == -1) {
070: // Split at first '.'
071: int pos = inName.indexOf('.');
072: String pre = (pos == -1 ? inName : inName.substring(0,
073: pos));
074: String post = (pos == -1 ? null : inName
075: .substring(pos + 1));
076: if (post != null && post.equals("class")) {
077: // Convert class name
078: outName = getOutName(pre) + ".class";
079: } else {
080: // Convert resource name
081: outName = getOutName(inName);
082: }
083:
084: // Store the converted name
085: if (outName != null) {
086: outNames.addElement(outName);
087: }
088: }
089: }
090: return outNames;
091: }
092:
093: // Map a name
094: private String getOutName(String inName) {
095: try {
096: TreeItem ti = root;
097: StringBuffer sb = new StringBuffer();
098: for (Enumeration nameEnum = ClassTree.getNameEnum(inName); nameEnum
099: .hasMoreElements();) {
100: SimpleName simpleName = (SimpleName) nameEnum
101: .nextElement();
102: String name = simpleName.getName();
103: if (simpleName.isAsPackage()) {
104: if (ti != null) {
105: ti = ((Pk) ti).getPackage(name);
106: if (ti != null) {
107: String repackageName = ((Pk) ti)
108: .getRepackageName();
109: if (repackageName != null) {
110: sb = new StringBuffer(repackageName);
111: } else {
112: sb.append(ti.getOutName());
113: }
114: } else {
115: sb.append(name);
116: }
117: } else {
118: sb.append(name);
119: }
120: sb.append(ClassTree.PACKAGE_LEVEL);
121: } else if (simpleName.isAsClass()) {
122: if (ti != null) {
123: ti = ((Pk) ti).getClass(name);
124: if (ti != null) {
125: sb.append(ti.getOutName());
126: } else {
127: sb.append(name);
128: }
129: } else {
130: sb.append(name);
131: }
132: return sb.toString();
133: } else {
134: throw new Exception(
135: "Internal error: illegal package/class name tag");
136: }
137: }
138: } catch (Exception e) {
139: // Just drop through and return the original name
140: }
141: return inName;
142: }
143:
144: /** Convert entries in file to obfuscated versions. */
145: public Vector toObfForConv(File listFile) throws Exception {
146: Vector outNames = new Vector();
147:
148: // Run through the listed names
149: for (Enumeration enm = getInNames(listFile); enm
150: .hasMoreElements();) {
151: // Convert the obfuscated portions
152: String inName = (String) enm.nextElement();
153: String outName = null;
154:
155: // Split at first '.'
156: int pos = inName.indexOf('.');
157: String pre = (pos == -1 ? inName : inName.substring(0, pos));
158: String post = (pos == -1 ? null : inName.substring(pos + 1));
159: if (post != null && post.equals("class")) {
160: // Convert class name
161: outName = getOutNameForConv(pre) + ".class";
162: } else {
163: // Convert resource name
164: outName = getOutNameForConv(inName);
165: }
166:
167: // Store the converted name
168: if (outName != null) {
169: outNames.addElement(outName);
170: }
171: }
172: return outNames;
173: }
174:
175: // Map a name
176: private String getOutNameForConv(String inName) {
177: try {
178: TreeItem ti = root;
179: StringBuffer sb = new StringBuffer();
180: for (Enumeration nameEnum = ClassTree.getNameEnum(inName); nameEnum
181: .hasMoreElements();) {
182: SimpleName simpleName = (SimpleName) nameEnum
183: .nextElement();
184: String name = simpleName.getName();
185: if (simpleName.isAsPackage()) {
186: if (ti != null) {
187: ti = ((Pk) ti).getPackage(name);
188: if (ti != null) {
189: String repackageName = ((Pk) ti)
190: .getRepackageName();
191: if (repackageName != null) {
192: sb = new StringBuffer(repackageName);
193: } else {
194: sb.append(ti.getOutName());
195: }
196: } else {
197: sb.append(name);
198: }
199: } else {
200: sb.append(name);
201: }
202: sb.append(ClassTree.PACKAGE_LEVEL);
203: } else if (simpleName.isAsClass()) {
204: if (ti != null) {
205: ti = ((PkCl) ti).getClass(name);
206: if (ti != null) {
207: sb.append(ti.getOutName());
208: } else {
209: sb.append(name);
210: }
211: } else {
212: sb.append(name);
213: }
214: if (nameEnum.hasMoreElements()) {
215: sb.append(ClassTree.CLASS_LEVEL);
216: } else {
217: return sb.toString();
218: }
219: } else {
220: throw new Exception(
221: "Internal error: illegal package/class name tag");
222: }
223: }
224: } catch (Exception e) {
225: // Just drop through and return the original name
226: }
227: return inName;
228: }
229:
230: // Parse the file names from the list
231: private Enumeration getInNames(File listFile) throws Exception {
232: InputStream is = listFile == null ? System.in
233: : new FileInputStream(listFile);
234: StreamTokenizer tk = new StreamTokenizer(new BufferedReader(
235: new InputStreamReader(is)));
236: tk.resetSyntax();
237: tk.whitespaceChars(0x00, 0x20);
238: tk.wordChars('*', '*');
239: tk.wordChars('.', '.');
240: tk.wordChars(';', ';');
241: tk.wordChars('_', '_');
242: tk.wordChars('[', '[');
243: tk.wordChars('(', ')');
244: tk.wordChars('$', '$');
245: tk.wordChars('/', '9');
246: tk.wordChars('A', 'Z');
247: tk.wordChars('a', 'z');
248: tk.commentChar('#');
249: tk.eolIsSignificant(false);
250: Vector inNames = new Vector();
251: try {
252: int ttype;
253: while ((ttype = tk.nextToken()) != StreamTokenizer.TT_EOF) {
254: if (ttype == StreamTokenizer.TT_WORD) {
255: inNames.addElement(tk.sval);
256: }
257: }
258: } finally {
259: return inNames.elements();
260: }
261: }
262:
263: // Create database from RGS log entries.
264: private void createHierarchy(RgsEnum rgsEnum) throws Exception {
265: // Create the root of the package hierarchy
266: root = new Pk(null, "");
267:
268: // Enumerate the entries in the RGS script
269: while (rgsEnum.hasMoreEntries()) {
270: RgsEntry entry = rgsEnum.nextEntry();
271: switch (entry.type) {
272: case RgsEntry.TYPE_PACKAGE_MAP:
273: addPackage(entry.name, entry.obfName);
274: break;
275:
276: case RgsEntry.TYPE_REPACKAGE_MAP:
277: addPackage(entry.name, null, entry.obfName);
278: break;
279:
280: case RgsEntry.TYPE_CLASS:
281: addClass(entry.name, getLastIdentifier(entry.name));
282: break;
283:
284: case RgsEntry.TYPE_CLASS_MAP:
285: addClass(entry.name, entry.obfName);
286: break;
287:
288: case RgsEntry.TYPE_METHOD:
289: addMethod(entry.name, entry.descriptor,
290: getLastIdentifier(entry.name));
291: break;
292:
293: case RgsEntry.TYPE_METHOD_MAP:
294: addMethod(entry.name, entry.descriptor, entry.obfName);
295: break;
296:
297: case RgsEntry.TYPE_ATTR:
298: case RgsEntry.TYPE_FIELD:
299: case RgsEntry.TYPE_FIELD_MAP:
300: // Ignore attribute and field entries
301: break;
302:
303: default:
304: throw new Exception(
305: "Illegal type received from the .rgs script");
306: }
307: }
308: }
309:
310: // Add a package to the hierarchy
311: private void addPackage(String fullName, String obfName)
312: throws Exception {
313: addPackage(fullName, obfName, null);
314: }
315:
316: // Add a package to the hierarchy
317: private void addPackage(String fullName, String obfName,
318: String repackageName) throws Exception {
319: TreeItem ti = root;
320: for (Enumeration nameEnum = ClassTree.getNameEnum(fullName); nameEnum
321: .hasMoreElements();) {
322: SimpleName simpleName = (SimpleName) nameEnum.nextElement();
323: String name = simpleName.getName();
324: ti = ((Pk) ti).addPackage(name);
325: }
326:
327: // Set the obfuscated name for the package
328: if (repackageName != null) {
329: ((Pk) ti).setRepackageName(repackageName);
330: ti.setOutName(ti.getInName());
331: } else {
332: ti.setOutName(obfName);
333: }
334: }
335:
336: // Add a class to the hierarchy
337: private Cl addClass(String fullName, String obfName)
338: throws Exception {
339: TreeItem ti = root;
340: for (Enumeration nameEnum = ClassTree.getNameEnum(fullName); nameEnum
341: .hasMoreElements();) {
342: SimpleName simpleName = (SimpleName) nameEnum.nextElement();
343: String name = simpleName.getName();
344: if (simpleName.isAsPackage()) {
345: ti = ((Pk) ti).addPackage(name);
346: } else if (simpleName.isAsClass()) {
347: // If inner class, just add placeholder classes up the tree
348: if (nameEnum.hasMoreElements()) {
349: ti = ((PkCl) ti).addPlaceholderClass(name);
350: } else {
351: ti = ((PkCl) ti).addClass(name, null, null, 0);
352: }
353: } else {
354: throw new Exception(
355: "Internal error: illegal package/class name tag");
356: }
357: }
358:
359: // Set the obfuscated name for the class (anonymous inner classes
360: // are already set, so leave them be)
361: if (obfName != null && !Character.isDigit(obfName.charAt(0))) {
362: ti.setOutName(obfName);
363: }
364: return (Cl) ti;
365: }
366:
367: // Add a method to the hierarchy
368: private void addMethod(String fullName, String descriptor,
369: String obfName) throws Exception {
370: // Split at last '/' - method name
371: int pos = fullName.lastIndexOf('/');
372: String className = fullName.substring(0, pos);
373: String methodName = fullName.substring(pos + 1);
374:
375: // Add the packaged class and the method elements
376: Cl cl = addClass(className, null);
377: Md md = cl.addMethod(false, methodName, descriptor, 0);
378: md.setOutName(obfName);
379: }
380:
381: // Return the last Java identifier from the passed String
382: private String getLastIdentifier(String name) {
383: String result = null;
384: if (name != null) {
385: int outer = name.lastIndexOf('/');
386: int inner = name.lastIndexOf('$');
387: if (outer != -1 || inner != -1) {
388: int pos = Math.max(inner, outer);
389: result = name.substring(pos + 1);
390: }
391: }
392: return result;
393: }
394:
395: /** Convert obfuscated stacktrace to ambiguous, original version. */
396: public void fromObfForTrace(File traceFile, PrintStream out)
397: throws Exception {
398: InputStream is = traceFile == null ? System.in
399: : new FileInputStream(traceFile);
400: StreamTokenizer tk = new StreamTokenizer(new BufferedReader(
401: new InputStreamReader(is)));
402: tk.resetSyntax();
403: tk.whitespaceChars(0x00, 0x20);
404: tk.wordChars('.', '.');
405: tk.wordChars(':', ':');
406: tk.wordChars('_', '_');
407: tk.wordChars('(', ')');
408: tk.wordChars('$', '$');
409: tk.wordChars('/', '9');
410: tk.wordChars('A', 'Z');
411: tk.wordChars('a', 'z');
412: tk.eolIsSignificant(false);
413: int ttype;
414: while ((ttype = tk.nextToken()) != StreamTokenizer.TT_EOF) {
415: if (ttype == StreamTokenizer.TT_WORD) {
416: String deobf = deobfTraceLine(tk.sval);
417: if (tk.sval.equals("at")) {
418: out.println();
419: out.print("\tat ");
420: } else if (deobf != null) {
421: out.print(deobf + " ");
422: } else {
423: out.print(tk.sval + " ");
424: }
425: }
426: }
427: out.println();
428: }
429:
430: // Deobfuscate a string containing an obfuscated method
431: private String deobfTraceLine(String line) {
432: // Split at first '(' if any
433: int pos = line.indexOf('(');
434: String obfFullMethod = (pos == -1) ? line : line.substring(0,
435: pos);
436: String rest = (pos == -1) ? "" : line.substring(pos);
437: String deobfMethod = getDeobfMethod(obfFullMethod.replace('.',
438: '/'));
439: if (deobfMethod == null) {
440: return null;
441: } else {
442: return deobfMethod.replace('/', '.') + rest;
443: }
444: }
445:
446: private String getDeobfMethod(String obfFullMethod) {
447: int pos = obfFullMethod.lastIndexOf('/');
448: if (pos == -1)
449: return null;
450: String className = obfFullMethod.substring(0, pos);
451: String methodName = obfFullMethod.substring(pos + 1);
452:
453: TreeItem ti = root;
454: StringBuffer sb = new StringBuffer();
455: try {
456: for (Enumeration nameEnum = ClassTree
457: .getNameEnum(className); nameEnum.hasMoreElements();) {
458: SimpleName simpleName = (SimpleName) nameEnum
459: .nextElement();
460: String name = simpleName.getName();
461: if (simpleName.isAsPackage()) {
462: if (ti != null) {
463: // Check regular obfuscated package names on this level
464: ti = ((Pk) ti).getObfPackage(name);
465: // Not found, so check for obfuscated name among
466: // repackaged names through the package hierarchy
467: if (ti == null) {
468: ti = root.getObfRepackage(name);
469: }
470: if (ti != null) {
471: String repackageName = ((Pk) ti)
472: .getRepackageName();
473: if (repackageName != null) {
474: sb = new StringBuffer(ti
475: .getFullInName());
476: } else {
477: sb.append(ti.getInName());
478: }
479: } else {
480: sb.append(name);
481: }
482: } else {
483: sb.append(name);
484: }
485: sb.append(ClassTree.PACKAGE_LEVEL);
486: } else if (simpleName.isAsClass()) {
487: if (ti != null) {
488: ti = ((PkCl) ti).getObfClass(name);
489: if (ti != null) {
490: sb.append(ti.getInName());
491: } else {
492: sb.append(name);
493: }
494: } else {
495: sb.append(name);
496: }
497: if (nameEnum.hasMoreElements()) {
498: sb.append(ClassTree.CLASS_LEVEL);
499: } else {
500: // Deobf. method name (multi-valued due to overloading)
501: sb.append('/');
502: if (ti != null) {
503: for (Enumeration mdEnum = ((Cl) ti)
504: .getObfMethods(methodName); mdEnum
505: .hasMoreElements();) {
506: sb.append('{');
507: sb.append(((Md) mdEnum.nextElement())
508: .getInName());
509: sb.append('}');
510: }
511: } else {
512: sb.append(methodName);
513: }
514: return sb.toString();
515: }
516: } else {
517: throw new Exception(
518: "Internal error: illegal package/class name tag");
519: }
520: }
521: } catch (Exception e) {
522: // Just drop through and return the original name
523: }
524: return obfFullMethod;
525: }
526: }
|