diff --git a/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/LibrariesPanel.java b/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/LibrariesPanel.java index 802d471..fa175d1 100644 --- a/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/LibrariesPanel.java +++ b/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/LibrariesPanel.java @@ -7,14 +7,8 @@ import dev.skidfuscator.obfuscator.Skidfuscator; import dev.skidfuscator.obfuscator.SkidfuscatorSession; import dev.skidfuscator.obfuscator.creator.SkidApplicationClassSource; -import dev.skidfuscator.obfuscator.util.MapleJarUtil; import org.mapleir.app.service.ApplicationClassSource; import org.mapleir.app.service.LibraryClassSource; -import dev.skidfuscator.jghost.GhostHelper; -import dev.skidfuscator.jghost.tree.GhostLibrary; -import dev.skidfuscator.obfuscator.Skidfuscator; -import dev.skidfuscator.obfuscator.creator.SkidApplicationClassSource; -import dev.skidfuscator.obfuscator.util.JdkDownloader; import javax.swing.*; import javax.swing.border.EtchedBorder; @@ -23,13 +17,16 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.List; -import java.net.URLEncoder; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import javax.swing.Timer; public class LibrariesPanel extends JPanel { private final JList libraryList; @@ -77,14 +74,38 @@ public LibrariesPanel(ConfigPanel configPanel, SkidApplicationClassSource classS BorderFactory.createEmptyBorder(20, 10, 10, 10) )); - // Create library list panel + // Create library list panel with buttons JPanel libraryListPanel = new JPanel(new BorderLayout()); libraryListPanel.setBorder(BorderFactory.createTitledBorder("Current Libraries")); + + // Create library list libraryModel = new DefaultListModel<>(); libraryList = new JList<>(libraryModel); libraryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane libraryScrollPane = new JScrollPane(libraryList); + + // Create library control buttons + JPanel libraryControlPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton addButton = new JButton("Add Library"); + JButton removeButton = new JButton("Remove Library"); + + addButton.addActionListener(e -> addManualLibrary()); + removeButton.addActionListener(e -> removeManualLibrary()); + + // Enable/disable remove button based on selection + libraryList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + removeButton.setEnabled(libraryList.getSelectedValue() != null); + } + }); + removeButton.setEnabled(false); + + libraryControlPanel.add(addButton); + libraryControlPanel.add(removeButton); + + // Add components to library panel libraryListPanel.add(libraryScrollPane, BorderLayout.CENTER); + libraryListPanel.add(libraryControlPanel, BorderLayout.SOUTH); // Create missing classes panel with side panel JPanel missingClassesPanel = new JPanel(new BorderLayout()); @@ -124,6 +145,10 @@ public LibrariesPanel(ConfigPanel configPanel, SkidApplicationClassSource classS missingClassesPanel.add(sidePanel, BorderLayout.EAST); + // Create split pane for lists + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, libraryListPanel, missingClassesPanel); + splitPane.setResizeWeight(0.5); + // Create status panel JPanel statusPanel = new JPanel(new BorderLayout(5, 5)); statusPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); @@ -140,14 +165,10 @@ public LibrariesPanel(ConfigPanel configPanel, SkidApplicationClassSource classS // Create button panel JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - scanButton = new JButton("Scan JAR"); + scanButton = new JButton("Rescan"); scanButton.addActionListener(this::onScanButtonClicked); buttonPanel.add(scanButton); - // Create split pane for lists - JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, libraryListPanel, missingClassesPanel); - splitPane.setResizeWeight(0.5); - // Create bottom panel for status and buttons JPanel bottomPanel = new JPanel(new BorderLayout()); bottomPanel.add(statusPanel, BorderLayout.CENTER); @@ -156,28 +177,107 @@ public LibrariesPanel(ConfigPanel configPanel, SkidApplicationClassSource classS // Add components to panel add(splitPane, BorderLayout.CENTER); add(bottomPanel, BorderLayout.SOUTH); + + // Analyze the input jar if specified in config + SwingUtilities.invokeLater(this::analyzeConfigJar); } - private void setStatus(String message, boolean error) { + private void setStatus(String message, boolean isError) { statusLabel.setText(message); - statusLabel.setForeground(error ? new Color(255, 65, 54) : new Color(46, 204, 64)); + statusLabel.setForeground(isError ? Color.RED : Color.WHITE); + if (isError) { + Skidfuscator.LOGGER.log(message); + } else { + Skidfuscator.LOGGER.log(message); + } } - private void refreshLibraryList() { - libraryModel.clear(); + private void refreshMissingClassesList() { + missingClassesModel.clear(); try { - Files.list(libraryFolder) - .filter(path -> path.toString().endsWith(".jar")) - .map(Path::getFileName) - .map(Path::toString) - .forEach(libraryModel::addElement); - } catch (IOException e) { - setStatus("Error refreshing library list: " + e.getMessage(), true); + classSource.getClassTree().verify(); + } catch (Exception e) { } + classSource.getMissingClassNames() + .forEach(missingClassesModel::addElement); + } + + private void refreshLibraryList() { + libraryModel.clear(); + classSource.getLibraries() + .stream() + .map(LibraryClassSource::getParent) + .map(ApplicationClassSource::getName) + .filter(e -> !e.endsWith(".jmod") + && !e.equalsIgnoreCase("rt.jar")) + .forEach(libraryModel::addElement); + } + + private void refreshInput(final File input) { + scanButton.setEnabled(false); + progressBar.setVisible(true); + progressBar.setIndeterminate(true); + setStatus("Scanning JAR file...", false); + SwingWorker, String> worker = new SwingWorker<>() { + @Override + protected void done() { + try { + List missingClasses = get(); + missingClassesModel.clear(); + for (String missingClass : missingClasses) { + missingClassesModel.addElement(missingClass); + } + setStatus("Found " + missingClasses.size() + " missing classes", false); + } catch (Exception ex) { + setStatus("Error scanning JAR: " + ex.getMessage(), true); + } finally { + scanButton.setEnabled(true); + progressBar.setVisible(false); + } + + } + + @Override + protected void process(List chunks) { + // Update status with the latest message + if (!chunks.isEmpty()) { + setStatus(chunks.get(chunks.size() - 1), false); + } + } + + @Override + protected List doInBackground() throws Exception { + publish("Initializing Skidfuscator..."); + Skidfuscator skidfuscator = new Skidfuscator(SkidfuscatorSession.builder() + .input(input) + .libs(libraryFolder.toFile().listFiles((dir, name) -> name.endsWith(".jar"))) + .build()); + + publish("Importing JVM classes..."); + + publish("Analyzing JAR file..."); + skidfuscator._importConfig(); + classSource = skidfuscator._importClasspath(); + final Set sources = skidfuscator._importJvm(); + classSource.addLibraries(sources.toArray(new LibraryClassSource[0])); + refreshLibraryList(); + refreshMissingClassesList(); + + publish("Verifying class tree..."); + try { + classSource.getClassTree().verify(); + } catch (Exception ex) { + // Ignore verification errors as we want to find missing classes + } + + return classSource.getClassTree().getMissingClasses(); + } + }; + worker.execute(); } private void onScanButtonClicked(ActionEvent e) { - JFileChooser fileChooser = new JFileChooser(); + /*JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileFilter(new javax.swing.filechooser.FileFilter() { public boolean accept(File f) { return f.isDirectory() || f.getName().toLowerCase().endsWith(".jar"); @@ -190,75 +290,58 @@ public String getDescription() { if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); - scanButton.setEnabled(false); - progressBar.setVisible(true); - progressBar.setIndeterminate(true); - setStatus("Scanning JAR file...", false); - - SwingWorker, String> worker = new SwingWorker<>() { - @Override - protected void done() { - try { - List missingClasses = get(); - missingClassesModel.clear(); - for (String missingClass : missingClasses) { - missingClassesModel.addElement(missingClass); - } - setStatus("Found " + missingClasses.size() + " missing classes", false); - } catch (Exception ex) { - setStatus("Error scanning JAR: " + ex.getMessage(), true); - } finally { - scanButton.setEnabled(true); - progressBar.setVisible(false); - } - - } - - @Override - protected void process(List chunks) { - // Update status with the latest message - if (!chunks.isEmpty()) { - setStatus(chunks.get(chunks.size() - 1), false); - } - } - - @Override - protected List doInBackground() throws Exception { - publish("Initializing Skidfuscator..."); - Skidfuscator skidfuscator = new Skidfuscator(SkidfuscatorSession.builder().build()); - - publish("Importing JVM classes..."); - final Set sources = skidfuscator._importJvm(); - - publish("Analyzing JAR file..."); - classSource = new SkidApplicationClassSource( - selectedFile.getName(), - false, - MapleJarUtil.importJar(selectedFile, skidfuscator).getJarContents(), - skidfuscator - ); - classSource.addLibraries(sources.toArray(new LibraryClassSource[0])); - - publish("Verifying class tree..."); - try { - classSource.getClassTree().verify(); - } catch (Exception ex) { - // Ignore verification errors as we want to find missing classes - } - - return classSource.getClassTree().getMissingClasses(); - } - }; - worker.execute(); - } + refreshInput(selectedFile); + }*/ + analyzeConfigJar(); } private void searchMavenCentral(String className, JButton sourceButton) { sourceButton.setEnabled(false); progressBar.setVisible(true); + progressBar.setValue(0); + progressBar.setIndeterminate(false); + progressBar.setStringPainted(true); setStatus("Searching Maven Central for " + className + "...", false); - SwingWorker, Void> worker = new SwingWorker<>() { + // Start the fake progress updater + Timer progressTimer = new Timer(100, null); + final long startTime = System.currentTimeMillis(); + final Random random = new Random(); + final AtomicInteger currentProgress = new AtomicInteger(0); + + progressTimer.addActionListener(e -> { + long elapsed = System.currentTimeMillis() - startTime; + if (elapsed >= 15000) { + progressTimer.stop(); + return; + } + + // Calculate target progress based on elapsed time (0-90%) + int targetProgress = (int) (elapsed * 90.0 / 15000.0); + + // Add some random variation + int currentValue = currentProgress.get(); + if (currentValue < targetProgress) { + int increment = random.nextInt(3) + 1; // Random increment between 1-3 + int newProgress = Math.min(currentValue + increment, targetProgress); + currentProgress.set(newProgress); + progressBar.setValue(newProgress); + + // Update status message occasionally + if (random.nextInt(10) == 0) { + String[] messages = { + "Searching Maven repositories...", + "Analyzing class dependencies...", + "Checking available versions...", + "Processing search results...", + "Querying Maven Central..." + }; + setStatus(messages[random.nextInt(messages.length)], false); + } + } + }); + + SwingWorker, String> worker = new SwingWorker<>() { @Override protected List doInBackground() throws Exception { String searchUrl = "https://search.maven.org/solrsearch/select?q=fc:" + URLEncoder.encode(className, StandardCharsets.UTF_8) + @@ -266,39 +349,89 @@ protected List doInBackground() throws Exception { HttpURLConnection connection = (HttpURLConnection) new URL(searchUrl).openConnection(); connection.setRequestMethod("GET"); + connection.setConnectTimeout(20000); // 20 seconds timeout + connection.setReadTimeout(20000); // 20 seconds timeout + connection.setRequestProperty("User-Agent", "Skidfuscator Library Manager"); + connection.setRequestProperty("Accept", "application/json"); + + // Start the progress timer + SwingUtilities.invokeLater(progressTimer::start); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - JsonObject response = gson.fromJson(reader, JsonObject.class); - JsonObject responseObj = response.getAsJsonObject("response"); - JsonArray docs = responseObj.getAsJsonArray("docs"); - - List artifacts = new ArrayList<>(); - for (JsonElement doc : docs) { - JsonObject artifact = doc.getAsJsonObject(); - artifacts.add(new MavenArtifact( - artifact.get("g").getAsString(), - artifact.get("a").getAsString(), - artifact.get("v").getAsString() - )); + // Connect and read response in background + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Server returned HTTP " + connection.getResponseCode() + + ": " + connection.getResponseMessage()); + } + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + return gson.fromJson(reader, JsonObject.class); + } + } catch (Exception e) { + throw new CompletionException(e); } - return artifacts; + }); + + // Wait for the response with timeout + JsonObject response; + try { + response = future.get(15, TimeUnit.SECONDS); + } catch (TimeoutException e) { + throw new IOException("Connection to Maven Central timed out"); + } + + // Stop the progress timer + SwingUtilities.invokeLater(progressTimer::stop); + + JsonObject responseObj = response.getAsJsonObject("response"); + JsonArray docs = responseObj.getAsJsonArray("docs"); + + progressBar.setValue(95); + List artifacts = new ArrayList<>(); + int total = docs.size(); + for (int i = 0; i < total; i++) { + JsonObject artifact = docs.get(i).getAsJsonObject(); + artifacts.add(new MavenArtifact( + artifact.get("g").getAsString(), + artifact.get("a").getAsString(), + artifact.get("v").getAsString() + )); } + progressBar.setValue(100); + return artifacts; } @Override protected void done() { + progressTimer.stop(); try { List artifacts = get(); if (artifacts.isEmpty()) { setStatus("No artifacts found for " + className, true); } else { + setStatus("Found " + artifacts.size() + " artifacts", false); MavenArtifact selected = showArtifactSelectionDialog(artifacts); if (selected != null) { downloadLibrary(selected); } } } catch (Exception e) { - setStatus("Error searching Maven Central: " + e.getMessage(), true); + String errorMsg; + if (e.getCause() instanceof TimeoutException || e.getCause() instanceof java.net.SocketTimeoutException) { + errorMsg = "Connection to Maven Central timed out. Please try again."; + } else { + errorMsg = "Error searching Maven Central: " + e.getMessage(); + } + setStatus(errorMsg, true); + JOptionPane.showMessageDialog( + LibrariesPanel.this, + errorMsg, + "Search Error", + JOptionPane.ERROR_MESSAGE + ); } finally { sourceButton.setEnabled(true); progressBar.setVisible(false); @@ -350,10 +483,16 @@ private MavenArtifact showArtifactSelectionDialog(List artifacts) private void downloadLibrary(MavenArtifact artifact) { setStatus("Downloading " + artifact + "...", false); progressBar.setVisible(true); + progressBar.setValue(0); + progressBar.setIndeterminate(false); + progressBar.setStringPainted(true); + progressBar.setMaximum(100); + progressBar.setMinimum(0); - SwingWorker worker = new SwingWorker<>() { + SwingWorker worker = new SwingWorker<>() { @Override - protected Void doInBackground() throws Exception { + protected File doInBackground() throws Exception { + progressBar.setVisible(true); String mavenUrl = String.format( "https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.jar", artifact.groupId.replace('.', '/'), @@ -363,48 +502,160 @@ protected Void doInBackground() throws Exception { artifact.version ); - Path outputFile = libraryFolder.resolve(artifact.artifactId + "-" + artifact.version + ".jar"); - try (InputStream in = new URL(mavenUrl).openStream(); - OutputStream out = Files.newOutputStream(outputFile)) { + HttpURLConnection connection = (HttpURLConnection) new URL(mavenUrl).openConnection(); + int fileSize = connection.getContentLength(); + + File outputFile = libraryFolder.resolve(artifact.artifactId + "-" + artifact.version + ".jar").toFile(); + try (InputStream in = new BufferedInputStream(connection.getInputStream()); + FileOutputStream out = new FileOutputStream(outputFile)) { byte[] buffer = new byte[8192]; int bytesRead; + long totalBytesRead = 0; + while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + if (fileSize > 0) { + publish((int) ((totalBytesRead * 100) / fileSize)); + } } } + return outputFile; + } - // Import the library into the class source - GhostLibrary library = GhostHelper.readFromLibraryFile( - Skidfuscator.LOGGER, - outputFile.toFile() - ); - classSource.importLibrary(outputFile.toFile()); - - return null; + @Override + protected void process(List chunks) { + if (!chunks.isEmpty()) { + Skidfuscator.LOGGER.log("Download progress: " + chunks.get(chunks.size() - 1) + "%"); + progressBar.setValue(chunks.get(chunks.size() - 1)); + } } @Override protected void done() { try { - get(); - setStatus("Successfully downloaded " + artifact, false); - refreshLibraryList(); - updateMissingClasses(); + File downloadedFile = get(); + setStatus("Importing library " + artifact + "...", false); + + try { + classSource.importLibrary(downloadedFile); + setStatus("Successfully imported " + artifact, false); + refreshLibraryList(); + refreshMissingClassesList(); + } catch (IOException e) { + String errorMsg = "Failed to import library: " + e.getMessage(); + setStatus(errorMsg, true); + JOptionPane.showMessageDialog( + LibrariesPanel.this, + errorMsg + "\nError details: " + e.toString(), + "Import Error", + JOptionPane.ERROR_MESSAGE + ); + // Clean up the downloaded file if import fails + if (!downloadedFile.delete()) { + downloadedFile.deleteOnExit(); + } + } } catch (Exception e) { - setStatus("Error downloading library: " + e.getMessage(), true); + String errorMsg = "Error downloading library: " + e.getMessage(); + setStatus(errorMsg, true); + JOptionPane.showMessageDialog( + LibrariesPanel.this, + errorMsg + "\nError details: " + e.toString(), + "Download Error", + JOptionPane.ERROR_MESSAGE + ); } finally { progressBar.setVisible(false); + progressBar.setStringPainted(false); } } }; worker.execute(); } - private void updateMissingClasses() { - Set missingClasses = classSource.getMissingClassNames(); - missingClassesModel.clear(); - for (String className : missingClasses) { - missingClassesModel.addElement(className); + private void addManualLibrary() { + FileDialog fileChooser = new FileDialog((Frame) null); + fileChooser.setVisible(true); + fileChooser.setMode(FileDialog.LOAD); + fileChooser.setFilenameFilter((f, name) -> f.isDirectory() || name.toLowerCase().endsWith(".jar")); + String selectedFileStr = fileChooser.getFile(); + if (selectedFileStr != null) { + File selectedFile = new File(fileChooser.getDirectory(), selectedFileStr); + + // Check if the file is outside the library folder + if (!selectedFile.getParentFile().equals(libraryFolder.toFile())) { + int result = JOptionPane.showConfirmDialog( + this, + "The selected library is outside the library folder.\n" + + "Would you like to copy it to the library folder?", + "Copy Library", + JOptionPane.YES_NO_OPTION + ); + + if (result == JOptionPane.YES_OPTION) { + try { + File destFile = libraryFolder.resolve(selectedFile.getName()).toFile(); + Files.copy(selectedFile.toPath(), destFile.toPath()); + selectedFile = destFile; + setStatus("Library copied to library folder", false); + } catch (IOException e) { + setStatus("Failed to copy library: " + e.getMessage(), true); + return; + } + } + } + + try { + classSource.importLibrary(selectedFile); + refreshLibraryList(); + refreshMissingClassesList(); + setStatus("Successfully imported " + selectedFile.getName(), false); + } catch (IOException e) { + setStatus("Failed to import library: " + e.getMessage(), true); + } + } + } + + private void removeManualLibrary() { + String selectedLibrary = libraryList.getSelectedValue(); + if (selectedLibrary != null) { + File libraryFile = libraryFolder.resolve(selectedLibrary).toFile(); + if (libraryFile.exists()) { + int result = JOptionPane.showConfirmDialog( + this, + "Are you sure you want to remove this library?\n" + + "This will also delete the file from the library folder.", + "Remove Library", + JOptionPane.YES_NO_OPTION + ); + + if (result == JOptionPane.YES_OPTION) { + if (libraryFile.delete()) { + setStatus("Successfully removed " + selectedLibrary, false); + classSource.getLibraries() + .removeIf(lib -> lib + .getParent() + .getName() + .equals(selectedLibrary) + ); + refreshMissingClassesList(); + refreshLibraryList(); + } else { + setStatus("Failed to remove library file", true); + } + } + } + } + } + + private void analyzeConfigJar() { + String inputPath = configPanel.getInputPath(); + if (inputPath != null && !inputPath.isEmpty()) { + File inputFile = new File(inputPath); + if (inputFile.exists()) { + refreshInput(inputFile); + } } } } \ No newline at end of file diff --git a/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/MainFrame.java b/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/MainFrame.java index 75ec0f4..a0758b5 100644 --- a/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/MainFrame.java +++ b/dev.skidfuscator.client.standalone/src/main/java/dev/skidfuscator/obfuscator/gui/MainFrame.java @@ -14,8 +14,12 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; @Getter public class MainFrame extends JFrame { @@ -25,6 +29,7 @@ public class MainFrame extends JFrame { private ConsolePanel consolePanel; private LibrariesPanel librariesPanel; private JButton startButton; + private JButton buyEnterpriseButton; private JPanel headerPanel; public MainFrame() { @@ -119,11 +124,27 @@ protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) { } }); + buyEnterpriseButton = new JButton("Buy Enterprise"); + buyEnterpriseButton.setFont(new Font("Segoe UI", Font.PLAIN, 12)); + buyEnterpriseButton.setBackground(Color.DARK_GRAY); + buyEnterpriseButton.setForeground(Color.WHITE); + buyEnterpriseButton.setFocusPainted(false); + buyEnterpriseButton.setPreferredSize(new Dimension(160, 30)); + + buyEnterpriseButton.addActionListener(e -> { + try { + Desktop.getDesktop().browse(new URI("https://skidfuscator.dev/pricing")); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + int topSpace = 220; int bottomPadding = this.getPreferredSize().height - 60*3; - JPanel buttonPanel = new JPanel(new BorderLayout()); - buttonPanel.add(startButton, BorderLayout.NORTH); + JPanel buttonPanel = new JPanel(new FlowLayout()); + buttonPanel.add(startButton); + buttonPanel.add(buyEnterpriseButton); // Add copyright and website info JPanel copyrightPanel = new JPanel(); @@ -299,14 +320,31 @@ public void startObfuscation() { } // Validate inputs - tabbedPane.setSelectedIndex(2); + tabbedPane.setSelectedIndex(3); + + // Validate libs + // Initialize library folder + String configLibPath = configPanel.getLibraryPath(); + Path libraryFolder; + if (configLibPath != null && !configLibPath.isEmpty()) { + libraryFolder = Paths.get(configLibPath); + } else { + libraryFolder = Paths.get(System.getProperty("user.home"), ".ssvm", "libs"); + } + + // Create library folder if it doesn't exist + try { + Files.createDirectories(libraryFolder); + } catch (IOException e) { + Skidfuscator.LOGGER.error("Failed to create library folder", e); + } // Create session SkidfuscatorSession session = SkidfuscatorSession.builder() .input(new File(config.getInputPath())) .output(new File(config.getOutputPath())) .libs(config.getLibsPath().isEmpty() - ? new File[0] + ? libraryFolder.toFile().listFiles() : new File(config.getLibsPath()).listFiles() ) .runtime(config.getRuntimePath().isEmpty() diff --git a/dev.skidfuscator.commons/src/main/java/dev/skidfuscator/jghost/GhostHelper.java b/dev.skidfuscator.commons/src/main/java/dev/skidfuscator/jghost/GhostHelper.java index 5d828be..2af3c66 100644 --- a/dev.skidfuscator.commons/src/main/java/dev/skidfuscator/jghost/GhostHelper.java +++ b/dev.skidfuscator.commons/src/main/java/dev/skidfuscator/jghost/GhostHelper.java @@ -98,7 +98,7 @@ public ApplicationClassSource getLibraryClassSource(final Logger logger, final b public ApplicationClassSource importFile(final Logger logger, final boolean fuckit, final GhostLibrary library) { /* Create a new library class source with superior to default priority */ final ApplicationClassSource libraryClassSource = new ApplicationClassSource( - "libraries", + library.getName(), fuckit, library.getContents() .getClasses() @@ -140,6 +140,7 @@ public GhostLibrary createFromLibraryFile(final Logger logger, final File file) final GhostContents ghostContents = new GhostContents(); final GhostLibrary ghostLibrary = new GhostLibrary(); + ghostLibrary.setName(file.getName()); ghostLibrary.setContents(ghostContents); try { diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java index 73bc490..e57843d 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java @@ -193,6 +193,7 @@ public Skidfuscator(SkidfuscatorSession session) { /* Builder */ .build(); + this.config = new DefaultSkidConfig(tsConfig, ""); } /** @@ -429,7 +430,7 @@ protected void _verifyEnvironment() { } } - protected void _importConfig() { + public void _importConfig() { LOGGER.post("Loading config..."); try (final ProgressWrapper progressBar = ProgressUtil.progressCheck( @@ -552,7 +553,7 @@ public Set _importJvm() { return sources; } - protected void _importClasspath() { + public SkidApplicationClassSource _importClasspath() { LOGGER.post("Importing jar..."); final String path = session.getInput().getPath(); @@ -673,6 +674,8 @@ protected void _importClasspath() { 1 ));*/ LOGGER.log("Finished importing classpath!"); + + return classSource; } protected List _loadTransformer() { diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/creator/SkidApplicationClassSource.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/creator/SkidApplicationClassSource.java index 88f0802..3586943 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/creator/SkidApplicationClassSource.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/creator/SkidApplicationClassSource.java @@ -14,10 +14,7 @@ import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class SkidApplicationClassSource extends ApplicationClassSource { @@ -57,25 +54,7 @@ public void importLibrary(File file) throws IOException { )); } - public Set getMissingClassNames() { - Set missingClasses = new HashSet<>(); - for (String className : nodeMap.keySet()) { - ClassNode node = nodeMap.get(className); - if (node != null) { - String superName = node.getName(); - if (superName != null && !contains(superName)) { - missingClasses.add(superName.replace('/', '.')); - } - Collection interfaces = node.getInterfaces(); - if (interfaces != null) { - for (String iface : interfaces) { - if (!contains(iface)) { - missingClasses.add(iface.replace('/', '.')); - } - } - } - } - } - return missingClasses; + public List getMissingClassNames() { + return classTree.getMissingClasses(); } } diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/hierarchy/SkidHierarchy.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/hierarchy/SkidHierarchy.java index c98a4eb..7b96026 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/hierarchy/SkidHierarchy.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/hierarchy/SkidHierarchy.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; +import java.util.function.BinaryOperator; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -371,12 +372,49 @@ private void setupInvoke() { if (!(targetClass instanceof SkidClassNode)) return; - assert targetClass.getMethods().size() == 1 : "Implicit Function must be single method!"; - final SkidMethodNode methodNode = (SkidMethodNode) targetClass.getMethods().get(0); - - methodNode.getGroup().setImplicitFunction(true); + SkidMethodNode methodNode = null; + + // [resolution] step 1: check if current class has method + ClassNode node; + + for (node = targetClass; + node instanceof SkidClassNode; + node = skidfuscator.getClassSource().findClassNode(targetClass.getSuperName())) { + if (!node.getMethods().isEmpty()) { + + // [validation] cannot have more than one method in implicit function + if (node.getMethods().size() > 1) { + throw new IllegalStateException(String.format( + """ + ----------------------------------------------------- + /!\\ Skidfuscator failed to verify a lambda call! + Please report this to the developer... + ----------------------------------------------------- + Bound: %s + Target: %s + Target Methods: %s + ----------------------------------------------------- + """, + boundFunc, + node.getDisplayName(), + node.getMethods().stream() + .map(MethodNode::toString) + .reduce("\n- ", (s, s2) -> s + "\n- " + s2) + + )); + } + + // must be correct + methodNode = (SkidMethodNode) node.getMethods().get(0); + break; + } + } + + if (methodNode != null) { + methodNode.getGroup().setImplicitFunction(true); + return; + } //System.out.println("Found implicit function: " + methodNode.toString()); - return; } target = new ClassMethodHash(boundFunc.getName(), boundFunc.getDesc(), boundFunc.getOwner()); diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java index c8fabdb..7751d14 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java @@ -133,8 +133,6 @@ public SkiddedHash hash(int starting, BasicBlock vertex, PredicateFlowGetter cal this.selectRandomMethod(); - System.out.println("Hashed: " + hashed + " Expr: " + hashExpr); - return new SkiddedHash(hashExpr, hashed); } catch (VMException | PanicException e) { e.printStackTrace(); diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/InstanceOfHashTransformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/InstanceOfHashTransformer.java index 78f455f..f5be4e2 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/InstanceOfHashTransformer.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/InstanceOfHashTransformer.java @@ -30,7 +30,7 @@ protected boolean matchesExpression(Expr expr) { final boolean valid = expr instanceof InstanceofExpr; if (valid) { - System.out.println("Checking instanceof expression: " + expr); + //System.out.println("Checking instanceof expression: " + expr); } return valid; @@ -46,7 +46,7 @@ protected boolean transformExpression(Expr expr, ControlFlowGraph cfg) { Type checkType = instanceofExpr.getCheckType(); Expr object = instanceofExpr.getExpression(); - System.out.println("Checking instanceof expression: " + expr); + //System.out.println("Checking instanceof expression: " + expr); // Skip primitive types and arrays if (checkType.getSort() != Type.OBJECT || checkType.getDescriptor().startsWith("[")) { @@ -69,7 +69,7 @@ protected boolean transformExpression(Expr expr, ControlFlowGraph cfg) { "(Ljava/lang/Object;Ljava/lang/String;I)Z" ); - System.out.println("Transformed instanceof expression: " + expr + " to " + replacement); + //System.out.println("Transformed instanceof expression: " + expr + " to " + replacement); expr.getParent().overwrite(expr, replacement); return true; } diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/TestSkidfuscator.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/TestSkidfuscator.java index c67baa1..d9a6d6f 100644 --- a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/TestSkidfuscator.java +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/TestSkidfuscator.java @@ -59,7 +59,7 @@ protected void _importExempt() { } @Override - protected void _importJvm() { + public Set _importJvm() { LOGGER.post("Beginning to import JVM..."); if (!cached) { _cacheJvm(this); @@ -72,10 +72,12 @@ protected void _importJvm() { )); } LOGGER.log("Finished importing JVM!"); + + return new HashSet<>(); } @Override - protected void _importClasspath() { + public SkidApplicationClassSource _importClasspath() { LOGGER.post("Beginning to import classpath..."); this.jarContents = new JarContents(); @@ -133,6 +135,8 @@ protected void _importClasspath() { this ); LOGGER.log("Finished importing classpath!"); + + return classSource; } @Override diff --git a/org.mapleir.parent/org.mapleir.app-services/src/main/java/org/mapleir/app/service/LibraryClassSource.java b/org.mapleir.parent/org.mapleir.app-services/src/main/java/org/mapleir/app/service/LibraryClassSource.java index af9e195..4831d8b 100644 --- a/org.mapleir.parent/org.mapleir.app-services/src/main/java/org/mapleir/app/service/LibraryClassSource.java +++ b/org.mapleir.parent/org.mapleir.app-services/src/main/java/org/mapleir/app/service/LibraryClassSource.java @@ -26,6 +26,10 @@ public LibraryClassSource(ApplicationClassSource parent, int priority) { this(Collections.emptySet(), parent, priority); } + public ApplicationClassSource getParent() { + return parent; + } + /* public lookup method, polls parent first (which can * call its children to look for the */ @Override