From 1b9f4f33def327a8b483ab81c2dd9e6b1b5d0a2b Mon Sep 17 00:00:00 2001 From: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:04:38 +0100 Subject: [PATCH] feat: Added export disassembler results to ASM file (#1987) ### Problem description This PR implements the feature request #1781, that suggests adding a button to export disassembled instructions into an ASM file. ### Implementation description This adds a button to export the current disassembled instructions to an ASM file. Said file is suffixed by an `.asm` extension if not specified at file creation. *Note: the file is written to for every `Disassembly` item in the vector, as it was the easiest and most memory-conservative way of doing it.* The file creation task is implemented based on IPS patch exports, so it fits the same pattern. A `ToastError` is raised when the ASM export could not complete successfully. Translations have been implemented for both `en_US` and `de_DE` for the two new keys: - `hex.disassembler.view.disassembler.export`: file export button - `hex.disassembler.view.disassembler.export.popup.error`: error popup text ### Screenshots The button is disabled when the disassembler is working, or when the disassembly vector is empty. Here is a complete breakdown of the visual changes: ![image](https://github.com/user-attachments/assets/af0ce701-9d77-45f1-9a5a-90d68d00bb0d) ### Additional things As expected, the exporter writes every item's `mnemonic` and `operators` to the file, producing an output like this: `example.asm` ```asm .byte 0x7f, 0x45, 0x4c, 0x46 andeq r0, r1, r2, lsl #2 andeq r0, r0, r0 andeq r0, r0, r0 eorseq r0, lr, r3 andeq r0, r0, r1 andeq r1, r0, r0, asr #32 andeq r0, r0, r0 andeq r0, r0, r0, asr #32 ``` --------- Signed-off-by: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com> --- .../content/views/view_disassembler.hpp | 1 + plugins/disassembler/romfs/lang/de_DE.json | 2 + plugins/disassembler/romfs/lang/en_US.json | 2 + .../content/views/view_disassembler.cpp | 42 ++++++++++++++++++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/plugins/disassembler/include/content/views/view_disassembler.hpp b/plugins/disassembler/include/content/views/view_disassembler.hpp index 3a29f190a..ec87d974c 100644 --- a/plugins/disassembler/include/content/views/view_disassembler.hpp +++ b/plugins/disassembler/include/content/views/view_disassembler.hpp @@ -41,6 +41,7 @@ namespace hex::plugin::disasm { std::vector m_disassembly; void disassemble(); + void exportToFile(); }; } \ No newline at end of file diff --git a/plugins/disassembler/romfs/lang/de_DE.json b/plugins/disassembler/romfs/lang/de_DE.json index 187cd2d39..7257f25a4 100644 --- a/plugins/disassembler/romfs/lang/de_DE.json +++ b/plugins/disassembler/romfs/lang/de_DE.json @@ -17,6 +17,8 @@ "hex.disassembler.view.disassembler.bpf.classic": "Classic", "hex.disassembler.view.disassembler.bpf.extended": "Extended", "hex.disassembler.view.disassembler.disassemble": "Disassemble", + "hex.disassembler.view.disassembler.export": "Exportieren", + "hex.disassembler.view.disassembler.export.popup.error": "Der Export zur ASM-Datei ist fehlgeschlagen.", "hex.disassembler.view.disassembler.disassembling": "Disassemblen...", "hex.disassembler.view.disassembler.disassembly.address": "Adresse", "hex.disassembler.view.disassembler.disassembly.bytes": "Byte", diff --git a/plugins/disassembler/romfs/lang/en_US.json b/plugins/disassembler/romfs/lang/en_US.json index d380fe47b..ef44b96c5 100644 --- a/plugins/disassembler/romfs/lang/en_US.json +++ b/plugins/disassembler/romfs/lang/en_US.json @@ -17,6 +17,8 @@ "hex.disassembler.view.disassembler.bpf.classic": "Classic", "hex.disassembler.view.disassembler.bpf.extended": "Extended", "hex.disassembler.view.disassembler.disassemble": "Disassemble", + "hex.disassembler.view.disassembler.export": "Export instructions as...", + "hex.disassembler.view.disassembler.export.popup.error": "Failed to export to ASM file!", "hex.disassembler.view.disassembler.disassembling": "Disassembling...", "hex.disassembler.view.disassembler.disassembly.address": "Address", "hex.disassembler.view.disassembler.disassembly.bytes": "Byte", diff --git a/plugins/disassembler/source/content/views/view_disassembler.cpp b/plugins/disassembler/source/content/views/view_disassembler.cpp index 19904b202..2727df2f1 100644 --- a/plugins/disassembler/source/content/views/view_disassembler.cpp +++ b/plugins/disassembler/source/content/views/view_disassembler.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace std::literals::string_literals; @@ -92,6 +93,36 @@ namespace hex::plugin::disasm { }); } + void ViewDisassembler::exportToFile() { + TaskManager::createTask("hex.ui.common.processing"_lang, TaskManager::NoProgress, [this](auto &) { + TaskManager::doLater([this] { + fs::openFileBrowser(fs::DialogMode::Save, {}, [this](const std::fs::path &path) { + auto p = path; + if (p.extension() != ".asm") + p.replace_filename(hex::format("{}{}", p.filename().string(), ".asm")); + auto file = wolv::io::File(p, wolv::io::File::Mode::Create); + + if (!file.isValid()) { + ui::ToastError::open("hex.disassembler.view.disassembler.export.popup.error"_lang); + return; + } + + // As disassembly code can be quite long, we prefer writing each disassembled instruction to file + for (const Disassembly& d : m_disassembly) { + // We test for a "bugged" case that should never happen - the instruction should always have a mnemonic + if (d.mnemonic.empty()) + continue; + + if (d.operators.empty()) + file.writeString(hex::format("{}\n", d.mnemonic)); + else + file.writeString(hex::format("{} {}\n", d.mnemonic, d.operators)); + } + }); + }); + }); + } + void ViewDisassembler::drawContent() { auto provider = ImHexApi::Provider::get(); if (ImHexApi::Provider::isValid() && provider->isReadable()) { @@ -390,9 +421,18 @@ namespace hex::plugin::disasm { } ImGui::EndDisabled(); + // Draw export to file icon button + ImGui::SameLine(); + ImGui::BeginDisabled(m_disassemblerTask.isRunning() || m_disassembly.empty()); + { + if (ImGuiExt::DimmedIconButton(ICON_VS_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text))) + this->exportToFile(); + } + ImGui::EndDisabled(); + ImGuiExt::InfoTooltip("hex.disassembler.view.disassembler.export"_lang); + // Draw a spinner if the disassembler is running if (m_disassemblerTask.isRunning()) { - ImGui::SameLine(); ImGuiExt::TextSpinner("hex.disassembler.view.disassembler.disassembling"_lang); }