diff --git a/build.gradle b/build.gradle index ab148a7..c2cb0fd 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,7 @@ dependencies { implementation 'org.graalvm.python:python-embedding:24.2.1' implementation files('libs/JNC-1.0-jnc.jar') + implementation files('libs/dog api 1.3.jar') implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本 diff --git a/language/saved_language.properties b/language/saved_language.properties index c155509..a8bcb83 100644 --- a/language/saved_language.properties +++ b/language/saved_language.properties @@ -1,3 +1,3 @@ #Current Loaded Language -#Thu May 01 15:04:30 CST 2025 +#Sat May 31 10:30:35 CST 2025 loadedLanguage=system\:zh_CN diff --git a/language/sys_zh_CN.properties b/language/sys_zh_CN.properties index 74b7cc8..09194d4 100644 --- a/language/sys_zh_CN.properties +++ b/language/sys_zh_CN.properties @@ -24,6 +24,7 @@ andShow.pluginInfo.writer=\u62A5\u544A\u5DF2\u4FDD\u5B58\u81F3: andShow.pluginInfo.writer.title=\u5BFC\u51FA\u6210\u529F andShow.pluginInfo.writer.error=\u5BFC\u51FA\u5931\u8D25: andShow.pluginInfo.error.writer.title=\u9519\u8BEF +save.crash.report=\u4FDD\u5B58\u5D29\u6E83\u62A5\u544A progressBarManager.title=\u52A0\u8F7D\u4E2D... @@ -116,4 +117,8 @@ settings.4.load_theme_success=\u4E3B\u9898 ' settings.4.load_theme_success.2=' \u5DF2\u6210\u529F\u52A0\u8F7D\uFF01 settings.4.load_theme_success.3=\u6210\u529F settings.4.load_theme_error=\u52A0\u8F7D\u4E3B\u9898\u5931\u8D25: -settings.4.load_theme_error.title=\u9519\u8BEF \ No newline at end of file +settings.4.load_theme_error.title=\u9519\u8BEF + +core.plugins=\u6838\u5FC3\u63D2\u4EF6 +plugin.info=\u63D2\u4EF6\u4FE1\u606F +error.details=\u9519\u8BEF\u8BE6\u60C5 \ No newline at end of file diff --git a/library/DogAgent.dll b/library/DogAgent.dll new file mode 100644 index 0000000..a6e16d2 Binary files /dev/null and b/library/DogAgent.dll differ diff --git a/libs/dog api 1.3.jar b/libs/dog api 1.3.jar new file mode 100644 index 0000000..c6730ff Binary files /dev/null and b/libs/dog api 1.3.jar differ diff --git a/mapping.properties b/mapping.properties index 5e69b74..25c3475 100644 --- a/mapping.properties +++ b/mapping.properties @@ -1,3 +1,4 @@ + #Obfuscation Mapping Table #Sun Feb 23 18:01:01 CST 2025 com/axis/innovators/box/AxisInnovatorsBox=a/b/c/d/A diff --git a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java index 1c4aa22..86916d5 100644 --- a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java +++ b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java @@ -20,16 +20,29 @@ import com.axis.innovators.box.verification.UserTags; import com.formdev.flatlaf.FlatLightLaf; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.api.dog.agent.VirtualMachine; +import org.jetbrains.annotations.NotNull; import org.tzd.lm.LM; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; +import java.awt.event.*; import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; +import java.lang.instrument.Instrumentation; +import java.lang.management.*; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; import java.util.List; -import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * 主类 @@ -69,6 +82,7 @@ public class AxisInnovatorsBox { try { LibraryLoad.loadLibrary("FridaNative"); LibraryLoad.loadLibrary("ThrowSafely"); + LibraryLoad.loadLibrary("DogAgent"); } catch (Exception e) { logger.error("Failed to load the 'FridaNative' library", e); } @@ -103,129 +117,567 @@ public class AxisInnovatorsBox { * 组织崩溃报告 */ public void organizingCrashReports(Exception e) { - String systemOut = Log4j2OutputStream.systemOutContent.toString(); - String systemErr = Log4j2OutputStream.systemErrContent.toString(); + SwingUtilities.invokeLater(() -> { + String systemOut = Log4j2OutputStream.systemOutContent.toString(); + String systemErr = Log4j2OutputStream.systemErrContent.toString(); - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String stackTrace = sw.toString(); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String stackTrace = sw.toString(); - String report = "========== 系统标准输出 ==========\n" + systemOut + - "\n========== 系统错误输出 ==========\n" + systemErr + - "\n========== 异常堆栈跟踪 ==========\n" + stackTrace; + String report = "========== 系统标准输出 ==========\n" + systemOut + + "\n========== 系统错误输出 ==========\n" + systemErr + + "\n========== 异常堆栈跟踪 ==========\n" + stackTrace; - SwingUtilities.invokeLater(() -> createAndShowGUI(report)); + SwingUtilities.invokeLater(() -> createAndShowGUI(report)); + }); } private void createAndShowGUI(String reportContent) { - JDialog dialog = new JDialog((Frame) null, - LanguageManager.getLoadedLanguages().getText("andShow.title"), true); - dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - dialog.setLayout(new BorderLayout(10, 10)); + // 使用 JFrame 作为顶级窗口 + JFrame dialog = new JFrame(LanguageManager.getLoadedLanguages().getText("andShow.title")); + dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception ignored) {} + // 1. 添加窗口关闭监听器,在关闭时调用 quit() + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + quit(); + } - JPanel topPanel = new JPanel(new BorderLayout(10, 10)); + @Override + public void windowClosing(WindowEvent e) { + quit(); + } + }); + + // 2. 设置窗口置顶 + dialog.setAlwaysOnTop(true); + + dialog.setLayout(new BorderLayout(15, 15)); + dialog.getRootPane().setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + // 顶部面板 - 使用卡片布局增强层次感 + JPanel topPanel = new JPanel(new BorderLayout(10, 15)); + topPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(220, 220, 220)), + BorderFactory.createEmptyBorder(15, 15, 15, 15) + )); + + // 图标和标题 + JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0)); Icon errorIcon = UIManager.getIcon("OptionPane.errorIcon"); JLabel iconLabel = new JLabel(errorIcon); JLabel titleLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.2")); + titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 16)); + titleLabel.setForeground(new Color(200, 0, 0)); + headerPanel.add(iconLabel); + headerPanel.add(titleLabel); + + // 说明文本 JLabel feedbackLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.3")); - feedbackLabel.setHorizontalAlignment(SwingConstants.CENTER); + feedbackLabel.setFont(feedbackLabel.getFont().deriveFont(14f)); + feedbackLabel.setForeground(new Color(80, 80, 80)); + feedbackLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); - JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); - titlePanel.add(iconLabel); - titlePanel.add(titleLabel); - - topPanel.add(titlePanel, BorderLayout.NORTH); + topPanel.add(headerPanel, BorderLayout.NORTH); topPanel.add(feedbackLabel, BorderLayout.CENTER); - topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + // 内容区域 - 使用现代样式 JTextArea contentArea = new JTextArea(reportContent); contentArea.setEditable(false); - contentArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); + contentArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13)); + contentArea.setBackground(new Color(30, 30, 35)); + contentArea.setCaretColor(Color.WHITE); + contentArea.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15)); + // 插件信息 StringBuilder pluginInfo = new StringBuilder(); - pluginInfo.append( - LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title")) - .append("\n"); + pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title")) + .append("\n\n"); List corePluginList = new ArrayList<>(); for (PluginDescriptor plugin : PluginLoader.getLoadedPlugins()) { - pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.1")).append(plugin.getName()).append("\n"); - pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.2")).append(plugin.getDescription()).append("\n"); - pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.3")).append(plugin.getSupportedVersions()).append("\n\n"); + pluginInfo.append("• ").append(plugin.getName()).append("\n"); + pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.2")) + .append(plugin.getDescription()).append("\n"); + pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.3")) + .append(plugin.getSupportedVersions()).append("\n\n"); if (plugin.getTransformers() != null) { corePluginList.add(plugin); } } - pluginInfo.append("=== 核心插件 ===\n"); + pluginInfo.append("=== ").append(LanguageManager.getLoadedLanguages().getText("core.plugins")).append(" ===\n\n"); for (PluginDescriptor corePlugin : corePluginList) { - pluginInfo.append("核心插件主类: ").append(corePlugin.getName()).append("\n"); - pluginInfo.append("核心转换器位置: ").append(corePlugin.getTransformers()).append("\n"); + pluginInfo.append("▶ ").append(corePlugin.getName()).append("\n"); + pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("transformer.location")) + .append(corePlugin.getTransformers()).append("\n"); } - contentArea.append("\n\n=== 插件信息 ===\n"); + contentArea.append("\n\n=== " + + LanguageManager.getLoadedLanguages().getText("plugin.info") + + " ===\n"); contentArea.append(pluginInfo.toString()); JScrollPane scrollPane = new JScrollPane(contentArea); - scrollPane.setBorder(BorderFactory.createTitledBorder("错误详细信息")); + scrollPane.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(180, 180, 180)), + LanguageManager.getLoadedLanguages().getText("error.details")), + BorderFactory.createEmptyBorder(5, 5, 5, 5) + )); - JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 15, 10)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); + + // 创建按钮 JButton exportButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.exportButton")); JButton closeButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.closeButton")); - exportButton.setForeground(Color.BLACK); - closeButton.setForeground(Color.BLACK); + // 设置按钮样式 + setupModernButton(exportButton, new Color(0, 115, 207), new Color(0, 95, 180)); + setupModernButton(closeButton, new Color(100, 100, 100), new Color(70, 70, 70)); - exportButton.setBackground(new Color(0, 120, 215)); - exportButton.setFocusPainted(false); - closeButton.setBackground(new Color(79, 79, 79)); - closeButton.setFocusPainted(false); - - exportButton.addActionListener((ActionEvent e) -> { + exportButton.addActionListener(e -> { JFileChooser fileChooser = new JFileChooser(); - fileChooser.setDialogTitle("保存崩溃报告"); - fileChooser.setSelectedFile(new File("crash_report.txt")); + fileChooser.setDialogTitle(LanguageManager.getLoadedLanguages().getText("save.crash.report")); - int userSelection = fileChooser.showSaveDialog(dialog); - if (userSelection == JFileChooser.APPROVE_OPTION) { - File fileToSave = fileChooser.getSelectedFile(); - try (FileWriter writer = new FileWriter(fileToSave)) { - writer.write(reportContent + "\n" + pluginInfo); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + Date date = new Date(System.currentTimeMillis()); + String timestamp = formatter.format(date); + + TimeZone tz = TimeZone.getDefault(); + String timezone = tz.getDisplayName(tz.inDaylightTime(date), TimeZone.SHORT).replace(":", ""); + + String filename = "AxisInnovatorsBox崩溃诊断报告_" + timestamp + "_" + timezone + ".zip"; + fileChooser.setSelectedFile(new File(filename)); + + // 设置ZIP文件过滤器 + fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("ZIP Files", "zip")); + + if (fileChooser.showSaveDialog(dialog) == JFileChooser.APPROVE_OPTION) { + File zipFile = fileChooser.getSelectedFile(); + if (!zipFile.getName().toLowerCase().endsWith(".zip")) { + zipFile = new File(zipFile.getAbsolutePath() + ".zip"); + } + + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + // 1. 添加崩溃报告文件 + ZipEntry crashEntry = new ZipEntry("basic_information.txt"); + zos.putNextEntry(crashEntry); + String reportData = reportContent + "\n\n" + pluginInfo; + zos.write(reportData.getBytes(StandardCharsets.UTF_8)); + zos.closeEntry(); + + // 2. 添加log4j日志文件 + addLog4jLogsToZip(zos); + + // 3. 添加其他调试文件 + addDebugFilesToZip(zos); + + // 成功消息 + String message = LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer") + + "\n" + zipFile.getAbsolutePath(); JOptionPane.showMessageDialog(dialog, - LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer") - + "\n" + fileToSave.getAbsolutePath(), + message, LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.title"), JOptionPane.INFORMATION_MESSAGE); } catch (IOException ex) { + String errorMessage = LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.error") + + "\n" + ex.getMessage(); JOptionPane.showMessageDialog(dialog, - LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.error") - + "\n" + ex.getMessage(), + errorMessage, LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.error.writer.title"), JOptionPane.ERROR_MESSAGE); } } }); - closeButton.addActionListener(e -> quit()); + // 3. 添加错误音效并关闭窗口 + closeButton.addActionListener(e -> { + playErrorSound(); // 播放错误音效 + dialog.dispose(); // 关闭窗口 + quit(); // 调用退出函数 + }); buttonPanel.add(exportButton); buttonPanel.add(closeButton); + // 组装主界面 dialog.add(topPanel, BorderLayout.NORTH); dialog.add(scrollPane, BorderLayout.CENTER); dialog.add(buttonPanel, BorderLayout.SOUTH); - dialog.setSize(800, 600); + dialog.setSize(900, 650); dialog.setLocationRelativeTo(null); + + // 播放错误音效 + playErrorSound(); + + // 显示窗口 dialog.setVisible(true); } + // 播放错误音效的方法 + private void playErrorSound() { + try { + // 使用系统默认的错误音效 + Toolkit.getDefaultToolkit().beep(); + + // 或者播放自定义音效 + /* + File soundFile = new File("error_sound.wav"); + if (soundFile.exists()) { + AudioInputStream audioIn = AudioSystem.getAudioInputStream(soundFile); + Clip clip = AudioSystem.getClip(); + clip.open(audioIn); + clip.start(); + } + */ + } catch (Exception ex) { + // 忽略音效播放错误 + } + } + + private void addLog4jLogsToZip(ZipOutputStream zos) throws IOException { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + Set addedFiles = new HashSet<>(); + + // 获取所有配置的附加器 + Collection appenders = config.getAppenders().values(); + for (Appender appender : appenders) { + processAppender(zos, addedFiles, appender); + } + } + + private void processAppender(ZipOutputStream zos, Set addedFiles, + Appender appender) throws IOException { + if (appender instanceof FileAppender) { + FileAppender fileAppender = (FileAppender) appender; + String fileName = fileAppender.getFileName(); + + if (fileName != null && !addedFiles.contains(fileName)) { + addFileToZip(zos, new File(fileName), "logs/"); + addedFiles.add(fileName); + } + } + + if (appender instanceof RollingFileAppender rollingAppender) { + addRollingLogFiles(zos, rollingAppender, addedFiles); + } + } + + private void addRollingLogFiles(ZipOutputStream zos, RollingFileAppender appender, + Set addedFiles) throws IOException { + String fileName = appender.getFileName(); + if (fileName == null) { + return; + } + + File logFile = new File(fileName); + File logDir = logFile.getParentFile(); + String baseName = getString(appender); + + if (logDir != null && logDir.exists()) { + File[] logFiles = logDir.listFiles((dir, name) -> + name.startsWith(baseName) || name.startsWith("box")); + + if (logFiles != null) { + for (File file : logFiles) { + String absolutePath = file.getAbsolutePath(); + if (!addedFiles.contains(absolutePath)) { + addFileToZip(zos, file, "logs/"); + addedFiles.add(absolutePath); + } + } + } + } + } + + private static @NotNull String getString(RollingFileAppender appender) { + String baseName; + + String filePattern = appender.getFilePattern(); + if (filePattern != null) { + // 移除目录部分 + int lastSlash = Math.max(filePattern.lastIndexOf('/'), filePattern.lastIndexOf('\\')); + if (lastSlash > 0) { + filePattern = filePattern.substring(lastSlash + 1); + } + int patternStart = filePattern.indexOf('%'); + if (patternStart == -1) patternStart = filePattern.indexOf('$'); + + if (patternStart > 0) { + baseName = filePattern.substring(0, patternStart); + } else { + baseName = "box"; + } + } else { + baseName = "box"; + } + return baseName; + } + + private void addDebugFilesToZip(ZipOutputStream zos) throws IOException { + // 1. 添加系统信息文件 + addFileToZip(zos, generateSystemInfoFile(), "debug_files/system_info.txt"); + // 2. 添加JVM加载的类信息 + addFileToZip(zos, generateClassLoaderInfo(), "debug_files/class_loader_info.txt"); + // 3. 添加内存状态信息 + addFileToZip(zos, generateMemoryInfo(), "debug_files/memory_info.txt"); + // 4. 添加线程堆栈信息 + addFileToZip(zos, generateThreadDump(), "debug_files/thread_dump.txt"); + // 5. 添加GC信息 + addFileToZip(zos, generateGCInfo(), "debug_files/gc_info.txt"); + // 6. 添加系统属性 + addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt"); + // 7. 添加环境变量 + addFileToZip(zos, generateEnvironmentVariables(), "debug_files/environment_variables.txt"); + } + + private File generateClassLoaderInfo() throws IOException { + File tempFile = File.createTempFile("class_loader", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + writer.println("===== Class Loader Hierarchy ====="); + Instrumentation instrumentation = null; + try { + + VirtualMachine vm = VirtualMachine.getVirtualMachine(ProcessHandle.current().pid(), true); + instrumentation = vm.getInstrumentation(); + } catch (Exception e) { + writer.println("Failed to attach to VM: " + e.getMessage()); + e.printStackTrace(writer); + } + + if (instrumentation != null) { + writer.println("\n===== Loaded Classes ====="); + Class[] allClasses = instrumentation.getAllLoadedClasses(); + writer.println("Total Classes: " + allClasses.length); + + Map>> classesByLoader = Arrays.stream(allClasses) + .collect(Collectors.groupingBy( + cls -> cls.getClassLoader() == null ? + BootstrapClassLoader.INSTANCE : + cls.getClassLoader() + )); + + for (Map.Entry>> entry : classesByLoader.entrySet()) { + ClassLoader loader = entry.getKey(); + List> classes = entry.getValue(); + + String loaderName = (loader == BootstrapClassLoader.INSTANCE) ? + "Bootstrap ClassLoader" : + loader.toString(); + + writer.println("\nClassLoader: " + loaderName); + writer.println("Classes Count: " + classes.size()); + + for (Class cls : classes) { + writer.println(" - " + cls.getName()); + } + } + } + + } + tempFile.deleteOnExit(); + return tempFile; + } + + private static class BootstrapClassLoader extends ClassLoader { + static final BootstrapClassLoader INSTANCE = new BootstrapClassLoader(); + private BootstrapClassLoader() {} + + @Override + public String toString() { + return "Bootstrap ClassLoader"; + } + } + + private File generateMemoryInfo() throws IOException { + File tempFile = File.createTempFile("memory", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean(); + + writer.println("===== Heap Memory Usage ====="); + MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage(); + writer.println("Init: " + formatMemory(heapUsage.getInit())); + writer.println("Used: " + formatMemory(heapUsage.getUsed())); + writer.println("Committed: " + formatMemory(heapUsage.getCommitted())); + writer.println("Max: " + formatMemory(heapUsage.getMax())); + + writer.println("\n===== Non-Heap Memory Usage ====="); + MemoryUsage nonHeapUsage = memoryMxBean.getNonHeapMemoryUsage(); + writer.println("Init: " + formatMemory(nonHeapUsage.getInit())); + writer.println("Used: " + formatMemory(nonHeapUsage.getUsed())); + writer.println("Committed: " + formatMemory(nonHeapUsage.getCommitted())); + writer.println("Max: " + formatMemory(nonHeapUsage.getMax())); + + writer.println("\n===== Memory Pool Details ====="); + List pools = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean pool : pools) { + writer.println("\nPool: " + pool.getName()); + writer.println("Type: " + pool.getType()); + MemoryUsage usage = pool.getUsage(); + writer.println("Usage: " + formatMemory(usage.getUsed()) + " / " + formatMemory(usage.getCommitted())); + } + } + tempFile.deleteOnExit(); + return tempFile; + } + + private String formatMemory(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + char unit = "KMGTPE".charAt(exp - 1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), unit); + } + + private File generateThreadDump() throws IOException { + File tempFile = File.createTempFile("thread_dump", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMxBean.dumpAllThreads(true, true); + + writer.println("===== Thread Dump (" + threadInfos.length + " threads) ====="); + for (ThreadInfo threadInfo : threadInfos) { + writer.println("\nThread #" + threadInfo.getThreadId() + ": " + threadInfo.getThreadName()); + writer.println("State: " + threadInfo.getThreadState()); + writer.println("Stack Trace:"); + for (StackTraceElement stackTraceElement : threadInfo.getStackTrace()) { + writer.println(" " + stackTraceElement); + } + } + } + tempFile.deleteOnExit(); + return tempFile; + } + + private File generateGCInfo() throws IOException { + File tempFile = File.createTempFile("gc_", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); + + writer.println("===== Garbage Collection ====="); + for (GarbageCollectorMXBean gcBean : gcBeans) { + writer.println("\nCollector: " + gcBean.getName()); + writer.println("Collections: " + gcBean.getCollectionCount()); + writer.println("Time: " + gcBean.getCollectionTime() + " ms"); + } + } + tempFile.deleteOnExit(); + return tempFile; + } + + // 生成系统属性 + private File generateSystemProperties() throws IOException { + File tempFile = File.createTempFile("system_props", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + Properties props = System.getProperties(); + writer.println("===== System Properties ====="); + for (String name : props.stringPropertyNames()) { + writer.println(name + " = " + props.getProperty(name)); + } + } + tempFile.deleteOnExit(); + return tempFile; + } + + // 生成环境变量 + private File generateEnvironmentVariables() throws IOException { + File tempFile = File.createTempFile("env_vars", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + Map env = System.getenv(); + writer.println("===== Environment Variables ====="); + for (Map.Entry entry : env.entrySet()) { + writer.println(entry.getKey() + " = " + entry.getValue()); + } + } + tempFile.deleteOnExit(); + return tempFile; + } + + // 添加目录到ZIP(递归) + private void addDirectoryToZip(ZipOutputStream zos, File dir, String basePath) throws IOException { + if (!dir.exists() || !dir.isDirectory()) return; + + for (File file : dir.listFiles()) { + String entryPath = basePath + "/" + file.getName(); + if (file.isDirectory()) { + addDirectoryToZip(zos, file, entryPath); + } else { + addFileToZip(zos, file, entryPath); + } + } + } + + private void addFileToZip(ZipOutputStream zos, File file, String entryPath) throws IOException { + if (!file.exists()) return; + + String entryName = entryPath + file.getName(); + ZipEntry zipEntry = new ZipEntry(entryName); + zos.putNextEntry(zipEntry); + + try (FileInputStream fis = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int length; + while ((length = fis.read(buffer)) > 0) { + zos.write(buffer, 0, length); + } + } + zos.closeEntry(); + } + + // 生成系统信息文件的方法 + private File generateSystemInfoFile() throws IOException { + File tempFile = File.createTempFile("system_info", ".txt"); + try (PrintWriter writer = new PrintWriter(tempFile)) { + writer.println("===== System Information ====="); + writer.println("OS: " + System.getProperty("os.name")); + writer.println("Version: " + System.getProperty("os.version")); + writer.println("Java Version: " + System.getProperty("java.version")); + writer.println("User: " + System.getProperty("user.name")); + writer.println("\n===== Runtime Information ====="); + writer.println("Free Memory: " + Runtime.getRuntime().freeMemory() / (1024 * 1024) + " MB"); + writer.println("Max Memory: " + Runtime.getRuntime().maxMemory() / (1024 * 1024) + " MB"); + } + tempFile.deleteOnExit(); + return tempFile; + } + + + private void setupModernButton(JButton button, Color bgColor, Color hoverColor) { + button.setFont(button.getFont().deriveFont(Font.BOLD)); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(8, 20, 8, 20)); + button.setBackground(bgColor); + button.setForeground(Color.WHITE); + + button.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + button.setBackground(hoverColor); + } + + @Override + public void mouseExited(MouseEvent e) { + button.setBackground(bgColor); + } + }); + + button.getModel().addChangeListener(e -> { + if (button.getModel().isPressed()) { + button.setBackground(hoverColor.darker()); + } else { + button.setBackground(button.getModel().isRollover() ? hoverColor : bgColor); + } + }); + } + /** * 初始化Log4j2 */ @@ -316,9 +768,6 @@ public class AxisInnovatorsBox { windowsJDialog.repaint(); } - //public void execute(Runnable runnable){ - // thread. - //} /** * 重新加载窗口 @@ -394,7 +843,8 @@ public class AxisInnovatorsBox { main.runWindow(); } catch (Exception e) { logger.error("There was a problem starting the main thread", e); - main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + if (main.ex != null) + main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); main.organizingCrashReports(e); throw new RuntimeException(e); } @@ -407,9 +857,11 @@ public class AxisInnovatorsBox { throw new RuntimeException(e); } }, "TrayThread").start(); + } catch (Exception e) { logger.error("Failed to load plugins", e); - main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + if (main.ex != null) + main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); main.organizingCrashReports(e); throw new RuntimeException(e); } @@ -418,8 +870,10 @@ public class AxisInnovatorsBox { main.thread.start(); } catch (Exception e) { logger.error("In unexpected errors", e); - main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + if (main.ex != null) + main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); main.organizingCrashReports(e); + throw new RuntimeException(e); } } diff --git a/src/main/java/com/axis/innovators/box/Main.java b/src/main/java/com/axis/innovators/box/Main.java index ad5e0bd..21c9533 100644 --- a/src/main/java/com/axis/innovators/box/Main.java +++ b/src/main/java/com/axis/innovators/box/Main.java @@ -9,6 +9,12 @@ import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.register.LanguageManager; import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; import java.util.List; import java.util.Map; @@ -16,11 +22,27 @@ import java.util.Map; * @author tzdwindows 7 */ public class Main { + // 单实例锁文件位置(系统临时目录) + private static final String LOCK_FILE = System.getProperty("java.io.tmpdir") + "/axis_innovators_box.lock"; + private static FileLock lock = null; + private static RandomAccessFile lockFile = null; + private static FileChannel lockChannel = null; + public static void main(String[] args) { + if (!acquireLock()) { + JOptionPane.showMessageDialog( + null, + "程序已在运行中,无法启动多个实例", + "错误", + JOptionPane.ERROR_MESSAGE + ); + System.exit(1); + } + FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10); LanguageManager.loadSavedLanguage(); - if (LanguageManager.getLoadedLanguages() == null){ + if (LanguageManager.getLoadedLanguages() == null) { LanguageManager.loadLanguage("system:zh_CN"); } @@ -28,7 +50,7 @@ public class Main { for (Map fileInfo : validFiles) { String extension = fileInfo.get("extension"); String path = fileInfo.get("path"); - if (".jar".equals(extension)){ + if (".jar".equals(extension)) { SwingUtilities.invokeLater(() -> { try { UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); @@ -38,15 +60,63 @@ public class Main { ModernJarViewer viewer = new ModernJarViewer(null, path); viewer.setVisible(true); }); + releaseLock(); // 释放锁(窗口模式) return; } - if (".html".equals(extension)){ + if (".html".equals(extension)) { MainApplication.popupHTMLWindow(path); + releaseLock(); // 释放锁(窗口模式) return; } } AxisInnovatorsBox.run(args); } -} + + /** + * 尝试获取文件锁(单实例检查) + */ + private static boolean acquireLock() { + try { + lockFile = new RandomAccessFile(LOCK_FILE, "rw"); + lockChannel = lockFile.getChannel(); + + lock = lockChannel.tryLock(); + return lock != null; + } catch (OverlappingFileLockException e) { + return false; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 释放文件锁 + */ + private static void releaseLock() { + try { + if (lock != null && lock.isValid()) { + lock.release(); + } + if (lockChannel != null) { + lockChannel.close(); + } + if (lockFile != null) { + lockFile.close(); + } + // 可选:删除锁文件 + new File(LOCK_FILE).delete(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 添加JVM关闭钩子确保锁释放 + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + releaseLock(); + })); + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/gui/MainWindow.java b/src/main/java/com/axis/innovators/box/gui/MainWindow.java index 496fb3b..77e161d 100644 --- a/src/main/java/com/axis/innovators/box/gui/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/gui/MainWindow.java @@ -36,8 +36,6 @@ public class MainWindow extends JFrame { private final Map cardElevations = new HashMap<>(); private final Color CARD_COLOR = Color.WHITE; private final List categories = new ArrayList<>(); - private final boolean isBackground = true; - private final boolean isBlur = true; private SystemTray systemTray; //private TrayIcon trayIcon; @@ -62,7 +60,6 @@ public class MainWindow extends JFrame { categories.add(category); } - public void initUI() { setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title")); setDefaultCloseOperation(EXIT_ON_CLOSE); @@ -78,13 +75,14 @@ public class MainWindow extends JFrame { mainPanel.setOpaque(true); mainPanel.setLayout(new BorderLayout(20, 20)); - mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 30, 30)); mainPanel.add(createHeader(), BorderLayout.NORTH); mainPanel.add(createCategoryTabs(), BorderLayout.CENTER); + //mainPanel.add(createFooter(), BorderLayout.SOUTH); 底部菜单 add(mainPanel); - createSystemTray(); + //createSystemTray(); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -93,47 +91,25 @@ public class MainWindow extends JFrame { }); } - private void createSystemTray() { - if (!SystemTray.isSupported()) { - logger.error("系统托盘不支持!"); - return; - } + private JPanel createFooter() { + JPanel footer = new JPanel(); + footer.setLayout(new BoxLayout(footer, BoxLayout.X_AXIS)); + footer.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30)); - // 初始化系统托盘 - systemTray = SystemTray.getSystemTray(); + JLabel version = new JLabel("轴创工具箱 v1.0"); + version.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + version.setForeground(new Color(120, 120, 120)); - // 1. 加载并处理圆角图标(修正图像类型) - ImageIcon rawIcon = LoadIcon.loadIcon("logo.png", 64); - Image roundedImage = createRoundedIcon(rawIcon.getImage(), 16); + footer.add(version); + footer.add(Box.createHorizontalGlue()); - // 2. 创建支持中文的弹出菜单 - PopupMenu popup = new PopupMenu(); + JLabel status = new JLabel("已加载 " + categories.size() + " 个分类, " + + categories.stream().mapToInt(c -> c.getTools().size()).sum() + " 个工具"); + status.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + status.setForeground(new Color(120, 120, 120)); + footer.add(status); - // 3. 创建菜单项(使用标准AWT组件) - MenuItem openItem = createBaseMenuItem("打开主界面", e -> setVisible(true)); - MenuItem decompileItem = createBaseMenuItem("打开反编译工具", e -> - new ModernJarViewer(null).setVisible(true)); - MenuItem exitItem = createBaseMenuItem("退出程序", e -> AxisInnovatorsBox.getMain().quit()); - - // 4. 构建菜单结构 - popup.add(openItem); - popup.addSeparator(); - popup.add(decompileItem); - popup.addSeparator(); - popup.add(exitItem); - - // 5. 创建托盘图标 - //trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup); - //trayIcon.setImageAutoSize(true); - - // 6. 添加事件监听 - //addTrayEventListeners(); - - //try { - // systemTray.add(trayIcon); - //} catch (AWTException ex) { - // logger.error("添加系统托盘图标失败", ex); - //} + return footer; } // 基础菜单项创建方法(解决方法不存在问题) @@ -347,6 +323,8 @@ public class MainWindow extends JFrame { title.setFont(new Font("微软雅黑", Font.BOLD, 28)); title.setForeground(new Color(255, 255, 255)); + + JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 32)); settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS); settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));