diff --git a/maple-ir/org.mapleir.app-services/src/main/java/org/mapleir/app/service/ClassTree.java b/maple-ir/org.mapleir.app-services/src/main/java/org/mapleir/app/service/ClassTree.java index c7bc4420..900666c1 100644 --- a/maple-ir/org.mapleir.app-services/src/main/java/org/mapleir/app/service/ClassTree.java +++ b/maple-ir/org.mapleir.app-services/src/main/java/org/mapleir/app/service/ClassTree.java @@ -23,7 +23,7 @@ // so a dfs goes through edges towards the root public class ClassTree extends FastDirectedGraph { private static final Logger LOGGER = Logger.getLogger(ClassTree.class); - private static final boolean ALLOW_PHANTOM_CLASSES = true; + private static final boolean ALLOW_PHANTOM_CLASSES = false; private final ApplicationClassSource source; private final ClassNode rootNode; @@ -45,6 +45,12 @@ protected void init() { addVertex(node); } } + + public void verify() { + for (ClassNode node : source.iterateWithLibraries()) { + verifyVertex(node); + } + } public ClassNode getRootNode() { return rootNode; @@ -166,6 +172,32 @@ private ClassNode requestClass0(String name, String from) { throw new RuntimeException("request from " + from, e); } } + + public void verifyVertex(ClassNode cn) { + if(cn == null) { + throw new IllegalStateException("Vertex is null!"); + } + + if (!containsVertex(cn)) { + addVertex(cn); + } + + if(cn != rootNode) { + ClassNode sup = cn.node.superName != null + ? requestClass0(cn.node.superName, cn.getName()) + : rootNode; + if(sup == null) { + throw new IllegalStateException(String.format("No superclass %s for %s", cn.node.superName, cn.getName())); + } + + for (String s : cn.node.interfaces) { + ClassNode iface = requestClass0(s, cn.getName()); + if(iface == null) { + throw new IllegalStateException(String.format("No superinterface %s for %s", s, cn.getName())); + } + } + } + } @Override public boolean addVertex(ClassNode cn) { diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java index d166865b..6b2c25fb 100644 --- a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java @@ -383,6 +383,27 @@ public void run() { } LOGGER.log("Finished importing the JVM!"); + /* Checking for errors */ + LOGGER.post("Starting verification"); + try { + classSource.getClassTree().verify(); + } catch (Exception e) { + System.out.println( + "-----------------------------------------------------\n" + + "/!\\ Skidfuscator failed to compute some libraries!\n" + + "It it advised to read https://github.com/terminalsin/skidfuscator-java-obfuscator/wiki/Libraries\n" + + "\n" + + "Error: " + e.getMessage() + "\n" + + (e.getCause() == null + ? "\n" + : " " + e.getCause().getMessage() + "\n" + ) + + "-----------------------------------------------------\n" + ); + System.exit(1); + return; + } + /* Resolve context */ LOGGER.post("Resolving basic context..."); this.cxt = new BasicAnalysisContext.BasicContextBuilder() diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressBarRenderer.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressBarRenderer.java new file mode 100644 index 00000000..3261a2bd --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressBarRenderer.java @@ -0,0 +1,172 @@ +package dev.skidfuscator.obfuscator.util.progress; + +import me.tongfei.progressbar.ProgressBarRenderer; +import me.tongfei.progressbar.ProgressBarStyle; + +import java.text.DecimalFormat; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static dev.skidfuscator.obfuscator.util.progress.StringDisplayUtils.getStringDisplayLength; +import static dev.skidfuscator.obfuscator.util.progress.StringDisplayUtils.trimDisplayLength; + +/** + * Default progress bar renderer (see {@link ProgressBarRenderer}). + * @author Tongfei Chen + * @author Muhammet Sakarya + * @since 0.8.0 + */ +public class SkidProgressBarRenderer { + + private ProgressBarStyle style; + private String unitName; + private long unitSize; + private boolean isSpeedShown; + private DecimalFormat speedFormat; + private ChronoUnit speedUnit; + private Spinner values = new Spinner<>( + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏" + ); + + protected SkidProgressBarRenderer( + ProgressBarStyle style, + String unitName, + long unitSize, + boolean isSpeedShown, + DecimalFormat speedFormat, + ChronoUnit speedUnit + ) { + this.style = style; + this.unitName = unitName; + this.unitSize = unitSize; + this.isSpeedShown = isSpeedShown; + this.speedFormat = isSpeedShown && speedFormat == null ? new DecimalFormat() : speedFormat; + this.speedUnit = speedUnit; + } + + // Number of full blocks + protected int progressIntegralPart(SkidProgressState progress, int length) { + return (int)(progress.getNormalizedProgress() * length); + } + + protected int progressFractionalPart(SkidProgressState progress, int length) { + double p = progress.getNormalizedProgress() * length; + double fraction = (p - Math.floor(p)) ;//* style.fractionSymbols.length(); + return (int) Math.floor(fraction); + } + + protected String eta(SkidProgressState progress, Duration elapsed) { + if (progress.max <= 0 || progress.indefinite) return "?"; + else if (progress.current - progress.start == 0) return "?"; + else return Util.formatDuration( + elapsed.dividedBy(progress.current - progress.start).multipliedBy(progress.max - progress.current) + ); + } + + protected String percentage(SkidProgressState progress) { + String res; + if (progress.max <= 0 || progress.indefinite) res = "? %"; + else res = String.valueOf((int) Math.floor(100.0 * progress.current / progress.max)) + "%"; + return Util.repeat(' ', 4 - res.length()) + res; + } + + protected String ratio(SkidProgressState progress) { + String m = progress.indefinite ? "?" : String.valueOf(progress.max / unitSize); + String c = String.valueOf(progress.current / unitSize); + return Util.repeat(' ', m.length() - c.length()) + c + "/" + m + unitName; + } + + protected String speed(SkidProgressState progress, Duration elapsed) { + String suffix = "/s"; + double elapsedSeconds = elapsed.getSeconds(); + double elapsedInUnit = elapsedSeconds; + if (null != speedUnit) + switch (speedUnit) { + case MINUTES: + suffix = "/min"; + elapsedInUnit /= 60; + break; + case HOURS: + suffix = "/h"; + elapsedInUnit /= (60 * 60); + break; + case DAYS: + suffix = "/d"; + elapsedInUnit /= (60 * 60 * 24); + break; + } + + if (elapsedSeconds == 0) + return "?" + unitName + suffix; + double speed = (double) (progress.current - progress.start) / elapsedInUnit; + double speedWithUnit = speed / unitSize; + return speedFormat.format(speedWithUnit) + unitName + suffix; + } + + public String render(SkidProgressState progress, int maxLength) { + if (maxLength <= 0) { + return ""; + } + /* + + Instant currTime = Instant.now(); + Duration elapsed = Duration.between(progress.startInstant, currTime); + + String prefix = progress.taskName + " (" + percentage(progress) + ") " + style.leftBracket; + int prefixLength = getStringDisplayLength(prefix); + + if (prefixLength > maxLength) { + prefix = trimDisplayLength(prefix, maxLength - 1); + prefixLength = maxLength - 1; + } + + // length of progress should be at least 1 + int maxSuffixLength = Math.max(maxLength - prefixLength - 1, 0); + + String speedString = isSpeedShown ? speed(progress, elapsed) : ""; + String suffix = style.rightBracket + " " + ratio(progress) + " (" + + Util.formatDuration(elapsed) + " / " + eta(progress, elapsed) + ") " + + speedString + progress.extraMessage; + int suffixLength = getStringDisplayLength(suffix); + // trim excessive suffix + if (suffixLength > maxSuffixLength) { + suffix = trimDisplayLength(suffix, maxSuffixLength); + suffixLength = maxSuffixLength; + } + + int length = maxLength - prefixLength - suffixLength; + + StringBuilder sb = new StringBuilder(maxLength); + sb.append(prefix); + + // case of indefinite progress bars + if (progress.indefinite) { + int pos = (int)(progress.current % length); + sb.append(Util.repeat(' ', pos)); + sb.append(values.next()); + sb.append(Util.repeat(' ', length - pos - 1)); + } + // case of definite progress bars + else { + sb.append(Util.repeat(style.block, progressIntegralPart(progress, length))); + if (progress.current < progress.max) { + sb.append(style.fractionSymbols.charAt(progressFractionalPart(progress, length))); + sb.append(Util.repeat(style.space, length - progressIntegralPart(progress, length) - 1)); + } + } + + sb.append(suffix);*/ + return null; //sb.toString(); + } + +} diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressState.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressState.java new file mode 100644 index 00000000..e918371e --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/SkidProgressState.java @@ -0,0 +1,111 @@ +package dev.skidfuscator.obfuscator.util.progress; + +import java.time.Duration; +import java.time.Instant; + +/** + * Encapsulates the internal states of a progress bar. + * @author Tongfei Chen + * @since 0.5.0 + */ +class SkidProgressState { + + String taskName; + String extraMessage = ""; + + boolean indefinite = false; + + // 0 start current max + // [===============|=========> ] + long start; + long current; + long max; + + Instant startInstant = null; + Duration elapsedBeforeStart = Duration.ZERO; + + volatile boolean alive = true; + volatile boolean paused = false; + + SkidProgressState(String taskName, long initialMax, long startFrom, Duration elapsedBeforeStart) { + this.taskName = taskName; + this.max = initialMax; + if (initialMax < 0) indefinite = true; + this.start = startFrom; + this.current = startFrom; + this.elapsedBeforeStart = elapsedBeforeStart; + this.startInstant = Instant.now(); + } + + String getTaskName() { + return taskName; + } + + synchronized String getExtraMessage() { + return extraMessage; + } + + synchronized long getCurrent() { + return current; + } + + synchronized long getMax() { + return max; + } + + // The progress, normalized to range [0, 1]. + synchronized double getNormalizedProgress() { + if (max <= 0) return 0.0; + else if (current > max) return 1.0; + else return ((double)current) / max; + } + + synchronized void setAsDefinite() { + indefinite = false; + } + + synchronized void setAsIndefinite() { + indefinite = true; + } + + synchronized void maxHint(long n) { + max = n; + } + + synchronized void stepBy(long n) { + current += n; + if (current > max) max = current; + } + + synchronized void stepTo(long n) { + current = n; + if (current > max) max = current; + } + + synchronized void setExtraMessage(String msg) { + extraMessage = msg; + } + + synchronized void pause() { + paused = true; + start = current; + elapsedBeforeStart = elapsedBeforeStart.plus(Duration.between(startInstant, Instant.now())); + } + + synchronized void resume() { + paused = false; + startInstant = Instant.now(); + } + + synchronized void reset() { + start = 0; + current = 0; + startInstant = Instant.now(); + elapsedBeforeStart = Duration.ZERO; + } + + synchronized void kill() { + alive = false; + } + +} diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Spinner.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Spinner.java new file mode 100644 index 00000000..226582f5 --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Spinner.java @@ -0,0 +1,20 @@ +package dev.skidfuscator.obfuscator.util.progress; + +/** + * @author Ghast + * @since 07/02/2021 + * Artemis © 2021 + */ +public class Spinner { + private final T[] values; + private int index; + + public Spinner(T... values) { + this.values = values; + this.index = 0; + } + + public T next() { + return values[index = (++index % values.length)]; + } +} \ No newline at end of file diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/StringDisplayUtils.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/StringDisplayUtils.java new file mode 100644 index 00000000..395a51c3 --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/StringDisplayUtils.java @@ -0,0 +1,38 @@ +package dev.skidfuscator.obfuscator.util.progress; + +import org.jline.utils.WCWidth; +class StringDisplayUtils { + StringDisplayUtils() { + } + + static int getCharDisplayLength(char c) { + return Math.max(WCWidth.wcwidth(c), 0); + } + + static int getStringDisplayLength(String s) { + int displayWidth = 0; + + for(int i = 0; i < s.length(); ++i) { + displayWidth += getCharDisplayLength(s.charAt(i)); + } + + return displayWidth; + } + + static String trimDisplayLength(String s, int maxDisplayLength) { + if (maxDisplayLength <= 0) { + return ""; + } else { + int totalLength = 0; + + for(int i = 0; i < s.length(); ++i) { + totalLength += getCharDisplayLength(s.charAt(i)); + if (totalLength > maxDisplayLength) { + return s.substring(0, i); + } + } + + return s; + } + } +} \ No newline at end of file diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/TerminalUtils.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/TerminalUtils.java new file mode 100644 index 00000000..4a1cd23e --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/TerminalUtils.java @@ -0,0 +1,77 @@ +package dev.skidfuscator.obfuscator.util.progress; + +import java.io.IOException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Stream; + +import me.tongfei.progressbar.ProgressBarConsumer; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.InfoCmp.Capability; + +public class TerminalUtils { + static final char CARRIAGE_RETURN = '\r'; + static final char ESCAPE_CHAR = '\u001b'; + static final int DEFAULT_TERMINAL_WIDTH = 80; + private static Terminal terminal = null; + private static boolean cursorMovementSupported = false; + static Queue activeConsumers = new ConcurrentLinkedQueue(); + + public TerminalUtils() { + } + + static synchronized int getTerminalWidth() { + Terminal terminal = getTerminal(); + int width = terminal.getWidth(); + return width >= 10 ? width : 80; + } + + static boolean hasCursorMovementSupport() { + if (terminal == null) { + terminal = getTerminal(); + } + + return cursorMovementSupported; + } + + static synchronized void closeTerminal() { + try { + if (terminal != null) { + terminal.close(); + terminal = null; + } + } catch (IOException var1) { + } + + } + + static Stream filterActiveConsumers(Class clazz) { + Stream var10000 = activeConsumers.stream(); + clazz.getClass(); + var10000 = var10000.filter(clazz::isInstance); + clazz.getClass(); + return var10000.map(clazz::cast); + } + + static String moveCursorUp(int count) { + return "\u001b[" + count + "A" + '\r'; + } + + static String moveCursorDown(int count) { + return "\u001b[" + count + "B" + '\r'; + } + + static Terminal getTerminal() { + if (terminal == null) { + try { + terminal = TerminalBuilder.builder().dumb(true).build(); + cursorMovementSupported = terminal.getStringCapability(Capability.cursor_up) != null && terminal.getStringCapability(Capability.cursor_down) != null; + } catch (IOException var1) { + throw new RuntimeException("This should never happen! Dumb terminal should have been created."); + } + } + + return terminal; + } +} \ No newline at end of file diff --git a/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Util.java b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Util.java new file mode 100644 index 00000000..aac27eb9 --- /dev/null +++ b/obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/progress/Util.java @@ -0,0 +1,74 @@ +package dev.skidfuscator.obfuscator.util.progress; + +import me.tongfei.progressbar.ConsoleProgressBarConsumer; +import me.tongfei.progressbar.InteractiveConsoleProgressBarConsumer; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +class Util { + static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, (runnable) -> { + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setName("ProgressBar"); + thread.setDaemon(true); + return thread; + }); + + Util() { + } + + static ConsoleProgressBarConsumer createConsoleConsumer(int predefinedWidth) { + PrintStream real = new PrintStream(new FileOutputStream(FileDescriptor.err)); + return createConsoleConsumer(real, predefinedWidth); + } + + static ConsoleProgressBarConsumer createConsoleConsumer(PrintStream out) { + return createConsoleConsumer(out, -1); + } + + static ConsoleProgressBarConsumer createConsoleConsumer(PrintStream out, int predefinedWidth) { + return (ConsoleProgressBarConsumer)(TerminalUtils.hasCursorMovementSupport() ? new InteractiveConsoleProgressBarConsumer(out, predefinedWidth) : new ConsoleProgressBarConsumer(out, predefinedWidth)); + } + + static String repeat(char c, int n) { + if (n <= 0) { + return ""; + } else { + char[] s = new char[n]; + + for(int i = 0; i < n; ++i) { + s[i] = c; + } + + return new String(s); + } + } + + static String formatDuration(Duration d) { + long s = d.getSeconds(); + return String.format("%d:%02d:%02d", s / 3600L, s % 3600L / 60L, s % 60L); + } + + static long getInputStreamSize(InputStream is) { + try { + if (is instanceof FileInputStream) { + return ((FileInputStream)is).getChannel().size(); + } + + int available = is.available(); + if (available > 0) { + return (long)available; + } + } catch (IOException var2) { + } + + return -1L; + } +} \ No newline at end of file