createdProcesses = new ArrayList<>();
//Security Manager for dynamic analysis debugging
public static SecurityMan sm = new SecurityMan();
//Refactorer
public static Refactorer refactorer = new Refactorer();
//GSON Reference
public static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
//Threads
private static final Thread versionChecker = new Thread(new UpdateCheck(), "Version Checker");
private static final Thread pingBack = new Thread(new PingBack(), "Pingback");
private static final Thread installFatJar = new Thread(new InstallFatJar(), "Install Fat-Jar");
private static final Thread bootCheck = new Thread(new BootCheck(), "Boot Check");
/**
* Main startup
*
* @param args files you want to open or CLI
*/
public static void main(String[] args)
{
launchArgs = args;
//welcome message
System.out.print("Bytecode Viewer " + VERSION);
if (FAT_JAR)
System.out.print(" [Fat Jar]");
System.out.println(" - Created by @Konloch");
System.out.println("https://bytecodeviewer.com - https://the.bytecode.club");
//set the security manager
System.setSecurityManager(sm);
try
{
//precache settings file
SettingsSerializer.preloadSettingsFile();
//setup look and feel
Configuration.lafTheme.setLAF();
//set swing specific system properties
System.setProperty("swing.aatext", "true");
//setup swing components
viewer = new MainViewerGUI();
SwingUtilities.updateComponentTreeUI(viewer);
//load settings and set swing components state
SettingsSerializer.loadSettings();
Configuration.bootState = BootState.SETTINGS_LOADED;
//set translation language
if (!Settings.hasSetLanguageAsSystemLanguage)
MiscUtils.setLanguage(MiscUtils.guessLanguage());
//handle CLI
int CLI = CommandLineInput.parseCommandLine(args);
if (CLI == CommandLineInput.STOP)
return;
//load with shaded libraries
if (FAT_JAR)
{
installFatJar.start();
}
else //load through bootloader
{
bootCheck.start();
Boot.boot(args, CLI != CommandLineInput.GUI);
}
//CLI arguments say spawn the GUI
if (CLI == CommandLineInput.GUI)
{
BytecodeViewer.boot(false);
Configuration.bootState = BootState.GUI_SHOWING;
}
else //CLI arguments say keep it CLI
{
BytecodeViewer.boot(true);
CommandLineInput.executeCommandLine(args);
}
}
catch (Exception e)
{
BytecodeViewer.handleException(e);
}
}
/**
* Boot after all of the libraries have been loaded
*
* @param cli is it running CLI mode or not
*/
public static void boot(boolean cli)
{
//delete files in the temp folder
cleanupAsync();
//shutdown hooks
Runtime.getRuntime().addShutdownHook(new Thread(() ->
{
for (Process proc : createdProcesses)
proc.destroy();
SettingsSerializer.saveSettings();
cleanup();
}, "Shutdown Hook"));
//setup the viewer
viewer.calledAfterLoad();
//setup the recent files
Settings.resetRecentFilesMenu();
//ping back once on first boot to add to global user count
if (!Configuration.pingback)
{
pingBack.start();
Configuration.pingback = true;
}
//version checking
if (viewer.updateCheck.isSelected() && !DEV_MODE)
versionChecker.start();
//show the main UI
if (!cli)
viewer.setVisible(true);
//print startup time
System.out.println("Start up took " + ((System.currentTimeMillis() - Configuration.start) / 1000) + " seconds");
//request focus on GUI for hotkeys on start
if (!cli)
viewer.requestFocus();
//open files from launch args
if (!cli)
if (launchArgs.length >= 1)
for (String s : launchArgs)
openFiles(new File[]{new File(s)}, true);
}
/**
* Adds a resource container to BCVs resource container list
*/
public static void addResourceContainer(ResourceContainer container)
{
resourceContainers.put(container.name, container);
SwingUtilities.invokeLater(() ->
{
try {
viewer.resourcePane.addResourceContainer(container);
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Returns true if there is at least one file resource loaded
*/
public static boolean hasResources()
{
return !resourceContainers.isEmpty();
}
/**
* Returns true if there is currently a tab open with a resource inside of it
*/
public static boolean hasActiveResource()
{
return getActiveResource() != null;
}
/**
* Returns true if there is currently a tab open with a resource inside of it
*/
public static boolean isActiveResourceClass()
{
ResourceViewer resource = getActiveResource();
return resource instanceof ClassViewer;
}
/**
* Returns the currently opened & viewed resource
*/
public static ResourceViewer getActiveResource()
{
return BytecodeViewer.viewer.workPane.getActiveResource();
}
/**
* Returns the currently opened ClassNode
*
* @return the currently opened ClassNode
*/
public static ClassNode getCurrentlyOpenedClassNode()
{
return getActiveResource().resource.getResourceClassNode();
}
/**
* Returns the ClassNode by the specified name
*
* TODO anything relying on this should be rewritten to search using the resource container
*
* @param name the class name
* @return the ClassNode instance
*/
@Deprecated
public static ClassNode blindlySearchForClassNode(String name)
{
for (ResourceContainer container : resourceContainers.values())
{
ClassNode node = container.getClassNode(name);
if (node != null)
return node;
}
return null;
}
/**
* Returns the resource container by the specific name
*/
public static ResourceContainer getFileContainer(String name)
{
for (ResourceContainer container : resourceContainers.values())
if (container.name.equals(name))
return container;
return null;
}
/**
* Returns all of the loaded resource containers
*/
public static Collection getResourceContainers()
{
return resourceContainers.values();
}
/**
* Grabs the file contents of the loaded resources.
*
* TODO anything relying on this should be rewritten to use the resource container's getFileContents
*
* @param name the file name
* @return the file contents as a byte[]
*/
@Deprecated
public static byte[] getFileContents(String name)
{
for (ResourceContainer container : resourceContainers.values())
if (container.resourceFiles.containsKey(name))
return container.resourceFiles.get(name);
return null;
}
/**
* Grab the byte array from the loaded Class object by getting the resource from the classloader
*/
public static byte[] getClassFileBytes(Class> clazz) throws IOException
{
return ClassFileUtils.getClassFileBytes(clazz);
}
/**
* Gets all of the loaded classes as an array list
*
* TODO: remove this and replace it with:
* BytecodeViewer.getResourceContainers().forEach(container -> {
* execute(new ArrayList<>(container.resourceClasses.values()));
* });
*
* @return the loaded classes as an array list
*/
@Deprecated
public static ArrayList getLoadedClasses()
{
ArrayList a = new ArrayList<>();
for (ResourceContainer container : resourceContainers.values())
for (ClassNode c : container.resourceClasses.values())
if (!a.contains(c))
a.add(c);
return a;
}
/**
* Called any time refresh is called to automatically compile all of the compilable panes that're opened.
*/
public static boolean autoCompileSuccessful()
{
if (!BytecodeViewer.viewer.autoCompileOnRefresh.isSelected())
return true;
try
{
return compile(false, false);
}
catch (NullPointerException ignored)
{
return false;
}
}
/**
* Compile all of the compilable panes that're opened.
*
* @param message if it should send a message saying it's compiled sucessfully.
* @return true if no errors, false if it failed to compile.
*/
public static boolean compile(boolean message, boolean successAlert)
{
BytecodeViewer.updateBusyStatus(true);
boolean noErrors = true;
boolean actuallyTried = false;
for (java.awt.Component c : BytecodeViewer.viewer.workPane.getLoadedViewers())
{
if (c instanceof ClassViewer)
{
ClassViewer cv = (ClassViewer) c;
if (noErrors && !cv.bytecodeViewPanel1.compile())
noErrors = false;
if (noErrors && !cv.bytecodeViewPanel2.compile())
noErrors = false;
if (noErrors && !cv.bytecodeViewPanel3.compile())
noErrors = false;
if (cv.bytecodeViewPanel1.textArea != null && cv.bytecodeViewPanel1.textArea.isEditable())
actuallyTried = true;
if (cv.bytecodeViewPanel2.textArea != null && cv.bytecodeViewPanel2.textArea.isEditable())
actuallyTried = true;
if (cv.bytecodeViewPanel3.textArea != null && cv.bytecodeViewPanel3.textArea.isEditable())
actuallyTried = true;
}
}
if (message)
{
if (actuallyTried)
{
if (noErrors && successAlert)
BytecodeViewer.showMessage("Compiled Successfully.");
}
else
{
BytecodeViewer.showMessage("You have no editable panes opened, make one editable and try again.");
}
}
BytecodeViewer.updateBusyStatus(false);
return true;
}
/**
* Opens a file, optional if it should append to the recent files menu
*
* @param files the file(s) you wish to open
* @param recentFiles if it should append to the recent files menu
*/
public static void openFiles(final File[] files, boolean recentFiles)
{
if (recentFiles)
{
for (File f : files)
if (f.exists())
Settings.addRecentFile(f);
SettingsSerializer.saveSettingsAsync();
}
BytecodeViewer.updateBusyStatus(true);
Configuration.needsReDump = true;
Thread t = new Thread(new ImportResource(files), "Import Resource");
t.start();
}
/**
* Starts the specified plugin
*
* @param file the file of the plugin
*/
public static void startPlugin(File file)
{
if (!file.exists())
{
BytecodeViewer.showMessage("The plugin file " + file.getAbsolutePath() + " could not be found.");
Settings.removeRecentPlugin(file);
return;
}
try
{
PluginWriter writer = new PluginWriter(DiskReader.loadAsString(file.getAbsolutePath()), file.getName());
writer.setSourceFile(file);
writer.setVisible(true);
}
catch (Exception e)
{
BytecodeViewer.handleException(e);
}
Settings.addRecentPlugin(file);
}
/**
* Send a message to alert the user
*
* @param message the message you need to send
*/
public static void showMessage(String message)
{
ExtendedJOptionPane.showMessageDialog(viewer, message);
}
/**
* Send a message to alert the user
*/
public static String showInput(String message)
{
return ExtendedJOptionPane.showInputDialog(viewer, message);
}
/**
* Send a message to alert the user
*/
public static String showInput(String message, String title, String initialMessage)
{
return (String) ExtendedJOptionPane.showInputDialog(viewer, message, title,
QUESTION_MESSAGE, null, null, initialMessage);
}
/**
* Alerts the user the program is running something in the background
*/
public static void updateBusyStatus(boolean busyStatus)
{
viewer.updateBusyStatus(busyStatus);
}
/**
* Clears all active busy status icons
*/
public static void clearBusyStatus()
{
viewer.clearBusyStatus();
}
/**
* Returns true if there are no loaded resource classes
*/
public static boolean promptIfNoLoadedClasses()
{
if (BytecodeViewer.getLoadedClasses().isEmpty())
{
BytecodeViewer.showMessage(TranslatedStrings.FIRST_OPEN_A_CLASS.toString());
return true;
}
return false;
}
/**
* Returns true if there are no loaded resource classes
*/
public static boolean promptIfNoLoadedResources()
{
if (BytecodeViewer.resourceContainers.isEmpty())
{
BytecodeViewer.showMessage(TranslatedStrings.FIRST_OPEN_A_RESOURCE.toString());
return true;
}
return false;
}
/**
* Handle the exception by creating a new window for bug reporting
*/
public static void handleException(Throwable t)
{
handleException(t, ExceptionUI.KONLOCH);
}
/**
* Handle the exception by creating a new window for bug reporting
*/
public static void handleException(Throwable t, String author)
{
new ExceptionUI(t, author);
}
/**
* Refreshes the title on all of the opened tabs
*/
public static void updateAllClassNodeByteArrays()
{
resourceContainers.values().forEach(ResourceContainer::updateClassNodeBytes);
}
/**
* Refreshes the title on all of the opened tabs
*/
public static void refreshAllTabTitles()
{
for(int i = 0; i < BytecodeViewer.viewer.workPane.tabs.getTabCount(); i++)
{
ResourceViewer viewer = ((TabbedPane) BytecodeViewer.viewer.workPane.tabs.getTabComponentAt(i)).resource;
viewer.refreshTitle();
}
}
/**
* Refreshes the title on all of the opened tabs
*/
public static void refreshAllTabs()
{
new Thread(()->
{
updateBusyStatus(true);
for (int i = 0; i < BytecodeViewer.viewer.workPane.tabs.getTabCount(); i++)
{
ResourceViewer viewer = ((TabbedPane) BytecodeViewer.viewer.workPane.tabs.getTabComponentAt(i)).resource;
viewer.refresh(null);
}
updateBusyStatus(false);
}, "Refresh All Tabs").start();
}
/**
* Resets the workspace with optional user input required
*
* @param ask if should require user input or not
*/
public static void resetWorkspace(boolean ask)
{
if (ask)
{
MultipleChoiceDialog dialog = new MultipleChoiceDialog(TranslatedStrings.RESET_TITLE.toString(),
TranslatedStrings.RESET_CONFIRM.toString(),
new String[]{TranslatedStrings.YES.toString(), TranslatedStrings.NO.toString()});
if (dialog.promptChoice() != 0)
return;
}
resetWorkspace();
}
/**
* Resets the workspace
*/
public static void resetWorkspace()
{
BytecodeViewer.resourceContainers.clear();
LazyNameUtil.reset();
BytecodeViewer.viewer.resourcePane.resetWorkspace();
BytecodeViewer.viewer.workPane.resetWorkspace();
BytecodeViewer.viewer.searchBoxPane.resetWorkspace();
BCV.getClassNodeLoader().clear();
ResourceListIconRenderer.iconCache.clear();
}
/**
* Clears the temp directory
*/
public static void cleanupAsync()
{
Thread cleanupThread = new Thread(BytecodeViewer::cleanup, "Cleanup");
cleanupThread.start();
}
/**
* Clears the temp directory
*/
public static void cleanup()
{
File tempF = new File(tempDirectory);
try {
FileUtils.deleteDirectory(tempF);
} catch (Exception ignored) { }
while (!tempF.exists()) // keep making dirs
tempF.mkdir();
}
/**
* because Smali and Baksmali System.exit if it failed
*/
public static void exit(int i) { }
}