diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index 9dfe89e3e..028dd0818 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -17,6 +17,8 @@ #include +#include "task_manager.hpp" + using ImGuiDataType = int; using ImGuiInputTextFlags = int; struct ImColor; @@ -1004,7 +1006,14 @@ namespace hex { std::string mnemonic; std::string operands; - std::optional jumpDestination; + enum class Type { + Jump, + Call, + Return, + Other + } type; + + std::optional extraData; }; class Architecture { @@ -1017,7 +1026,7 @@ namespace hex { } virtual void drawConfigInterface() = 0; - virtual std::vector disassemble(prv::Provider *provider, const Region& region) = 0; + virtual std::vector disassemble(prv::Provider *provider, const Region& region, Task &task) = 0; private: std::string m_unlocalizedName; diff --git a/plugins/builtin/include/content/views/view_disassembler.hpp b/plugins/builtin/include/content/views/view_disassembler.hpp index 63dca9079..bd0da5a97 100644 --- a/plugins/builtin/include/content/views/view_disassembler.hpp +++ b/plugins/builtin/include/content/views/view_disassembler.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include namespace hex::plugin::builtin { @@ -17,20 +19,33 @@ namespace hex::plugin::builtin { private: struct DisassemblyLine { + enum class Type { + Instruction, + CallInstruction, + Separator, + } type; + ImHexApi::HexEditor::ProviderRegion region; std::string bytes; std::string mnemonic; std::string operands; - std::optional jumpDestination; - ImVec2 linePos; + std::optional extraData; + ImVec2 linePosition; }; void addLine(prv::Provider *provider, const ContentRegistry::Disassembler::Instruction &instruction); + bool drawInstructionLine(DisassemblyLine &line); + bool drawSeparatorLine(DisassemblyLine &line); private: PerProvider> m_lines; ContentRegistry::Disassembler::Architecture *m_currArchitecture = nullptr; + + ui::RegionType m_regionType = ui::RegionType::EntireData; + Region m_disassembleRegion = { 0, 0 }; + + TaskHolder m_disassembleTask; }; -} \ No newline at end of file +} diff --git a/plugins/builtin/include/ui/widgets.hpp b/plugins/builtin/include/ui/widgets.hpp index f3442b7a5..0ca240abc 100644 --- a/plugins/builtin/include/ui/widgets.hpp +++ b/plugins/builtin/include/ui/widgets.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include + namespace pl::ptrn { class Pattern; } namespace hex::prv { class Provider; } diff --git a/plugins/builtin/source/content/disassemblers.cpp b/plugins/builtin/source/content/disassemblers.cpp index a0125524c..0b17ea436 100644 --- a/plugins/builtin/source/content/disassemblers.cpp +++ b/plugins/builtin/source/content/disassemblers.cpp @@ -3,9 +3,14 @@ #include #include +#include #include #include +#include + +#include + namespace hex::plugin::builtin { namespace { @@ -17,15 +22,28 @@ namespace hex::plugin::builtin { public: ArchitectureCapstoneBase(const std::string &unlocalizedName, cs_arch arch) : Architecture(unlocalizedName), m_architecture(arch) { } - std::vector disassemble(prv::Provider *provider, const Region ®ion) override { + virtual Instruction::Type getInstructionType(const cs_insn &instruction) { + for (const auto &group : std::span { instruction.detail->groups, instruction.detail->groups_count }) { + if (group == CS_GRP_RET || group == CS_GRP_IRET) + return Instruction::Type::Return; + if (group == CS_GRP_CALL) + return Instruction::Type::Call; + if (group == CS_GRP_JUMP) + return Instruction::Type::Jump; + } + + return Instruction::Type::Other; + } + + std::vector disassemble(prv::Provider *provider, const Region ®ion, Task &task) override { std::vector disassembly; csh csHandle = {}; - if (cs_open(this->m_architecture, CS_MODE_64, &csHandle) != CS_ERR_OK) + if (cs_open(this->m_architecture, cs_mode(u64(this->m_mode) | (this->m_endian == 0 ? CS_MODE_LITTLE_ENDIAN : CS_MODE_BIG_ENDIAN)), &csHandle) != CS_ERR_OK) return {}; ON_SCOPE_EXIT { cs_close(&csHandle); }; - cs_option(csHandle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL); + cs_option(csHandle, CS_OPT_SYNTAX, this->m_syntax); cs_option(csHandle, CS_OPT_DETAIL, CS_OPT_ON); cs_option(csHandle, CS_OPT_SKIPDATA, CS_OPT_ON); @@ -35,6 +53,7 @@ namespace hex::plugin::builtin { std::vector bytes; u64 prevAddress = std::numeric_limits::max(); for (u64 address = region.getStartAddress(); address < region.getEndAddress();) { + task.update(address - region.getStartAddress()); if (prevAddress == address) break; bytes.resize(std::min(2_MiB, (region.getEndAddress() - address) + 1)); @@ -44,11 +63,13 @@ namespace hex::plugin::builtin { size_t size = bytes.size(); prevAddress = address; while (cs_disasm_iter(csHandle, &code, &size, &address, instruction)) { + auto type = getInstructionType(*instruction); auto line = Instruction { - .region = { instruction->address, instruction->size }, - .mnemonic = instruction->mnemonic, - .operands = instruction->op_str, - .jumpDestination = getJumpDestination(*instruction) + .region = { instruction->address, instruction->size }, + .mnemonic = instruction->mnemonic, + .operands = instruction->op_str, + .type = type, + .extraData = getExtraData(type, *instruction) }; disassembly.emplace_back(std::move(line)); @@ -59,48 +80,268 @@ namespace hex::plugin::builtin { } void drawConfigInterface() override { - ImGui::TextUnformatted("Config Interface"); + ImGuiExt::BeginSubWindow("Endianess"); + { + drawRadioButtons(this->m_endian, { + { "Little", CS_MODE_LITTLE_ENDIAN }, + { "Big", CS_MODE_BIG_ENDIAN } + }); + } + ImGuiExt::EndSubWindow(); + + ImGuiExt::BeginSubWindow("Syntax"); + { + drawRadioButtons(this->m_syntax, { + { "Intel", CS_OPT_SYNTAX_INTEL }, + { "AT&T", CS_OPT_SYNTAX_ATT }, + { "MASM", CS_OPT_SYNTAX_MASM }, + { "Motorola", CS_OPT_SYNTAX_MOTOROLA } + + }); + } + ImGuiExt::EndSubWindow(); } - virtual std::optional getJumpDestination(const cs_insn &instruction) = 0; + virtual std::optional getExtraData(Instruction::Type type, const cs_insn &instruction) = 0; + + protected: + template + static void drawRadioButtons(T &currMode, const std::vector> &modes) { + for (const auto &[index, mode] : modes | std::views::enumerate) { + const auto &[unlocalizedName, csMode] = mode; + + if (ImGui::RadioButton(Lang(unlocalizedName), csMode == currMode)) { + currMode = csMode; + } + + ImGui::SameLine(); + } + ImGui::NewLine(); + } + + template + static void drawCheckbox(T &currMode, const std::string &unlocalizedName, T mode) { + bool enabled = currMode & mode; + if (ImGui::Checkbox(Lang(unlocalizedName), &enabled)) { + if (enabled) + currMode = T(u64(currMode) | u64(mode)); + else + currMode = T(u64(currMode) & ~u64(mode)); + + } + } private: cs_arch m_architecture; + cs_mode m_endian = cs_mode(0); + cs_opt_value m_syntax = CS_OPT_SYNTAX_INTEL; + + protected: + cs_mode m_mode = cs_mode(0); }; class ArchitectureX86 : public ArchitectureCapstoneBase { public: - ArchitectureX86() : ArchitectureCapstoneBase("x86", CS_ARCH_X86) { } + ArchitectureX86() : ArchitectureCapstoneBase("x86", CS_ARCH_X86) { + this->m_mode = CS_MODE_64; + } - std::optional getJumpDestination(const cs_insn &instruction) override { - // Get jump destination of jumps on x86 - if (instruction.id == X86_INS_JMP) { - if (instruction.detail->x86.op_count != 1) - return std::nullopt; + std::optional getExtraData(Instruction::Type type, const cs_insn &instruction) override { + switch (type) { + using enum Instruction::Type; - const auto &op = instruction.detail->x86.operands[0]; + case Jump: { + // Get jump destination of jumps on x86 + if (instruction.id == X86_INS_JMP) { + if (instruction.detail->x86.op_count != 1) + return std::nullopt; - if (op.type == X86_OP_IMM) - return op.imm; + const auto &op = instruction.detail->x86.operands[0]; - if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP) - return instruction.address + instruction.size + op.mem.disp; + if (op.type == X86_OP_IMM) + return op.imm; + + if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP) + return instruction.address + instruction.size + op.mem.disp; + } + + // Get jump destination of conditional jumps on x86 + if (instruction.id >= X86_INS_JAE && instruction.id <= X86_INS_JLE) { + if (instruction.detail->x86.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->x86.operands[0]; + + if (op.type == X86_OP_IMM) + return op.imm; + + if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Call: { + if (instruction.id == X86_INS_CALL) { + if (instruction.detail->x86.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->x86.operands[0]; + + if (op.type == X86_OP_IMM) + return op.imm; + + if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Return: + break; + case Other: + break; } - // Get jump destination of conditional jumps on x86 - if (instruction.id >= X86_INS_JAE && instruction.id <= X86_INS_JLE) { - if (instruction.detail->x86.op_count != 1) - return std::nullopt; - const auto &op = instruction.detail->x86.operands[0]; + return std::nullopt; + } - if (op.type == X86_OP_IMM) - return op.imm; + void drawConfigInterface() override { + ArchitectureCapstoneBase::drawConfigInterface(); - if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP) - return instruction.address + instruction.size + op.mem.disp; + ImGuiExt::BeginSubWindow("Address Width"); + { + drawRadioButtons(this->m_mode, { + { "16 Bit", CS_MODE_16 }, + { "32 Bit", CS_MODE_32 }, + { "64 Bit", CS_MODE_64 }, + }); } + ImGuiExt::EndSubWindow(); + } + }; + + class ArchitectureARM32 : public ArchitectureCapstoneBase { + public: + ArchitectureARM32() : ArchitectureCapstoneBase("ARM", CS_ARCH_ARM) { + this->m_mode = CS_MODE_ARM; + } + + std::optional getExtraData(Instruction::Type type, const cs_insn &instruction) override { + switch (type) { + using enum Instruction::Type; + + case Jump: { + // Get jump destination of jumps on ARM + if (instruction.id == ARM_INS_B) { + if (instruction.detail->arm.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->arm.operands[0]; + + if (op.type == ARM_OP_IMM) + return op.imm; + + if (op.type == ARM_OP_MEM && op.mem.base == ARM_REG_PC) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Call: { + if (instruction.id == ARM_INS_BL) { + if (instruction.detail->arm.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->arm.operands[0]; + + if (op.type == ARM_OP_IMM) + return op.imm; + + if (op.type == ARM_OP_MEM && op.mem.base == ARM_REG_PC) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Return: + break; + case Other: + break; + } + + + return std::nullopt; + } + + void drawConfigInterface() override { + ArchitectureCapstoneBase::drawConfigInterface(); + + ImGuiExt::BeginSubWindow("Instruction Set"); + { + drawRadioButtons(this->m_mode, { + { "ARM", CS_MODE_ARM }, + { "Thumb & Thumb-2", CS_MODE_THUMB }, + { "ARMv8 / AArch32", CS_MODE_THUMB } + }); + + drawCheckbox(this->m_mode, "Cortex-M", CS_MODE_MCLASS); + } + ImGuiExt::EndSubWindow(); + } + }; + + class ArchitectureARM64 : public ArchitectureCapstoneBase { + public: + ArchitectureARM64() : ArchitectureCapstoneBase("ARM64 / AArch64", CS_ARCH_ARM64) { + } + + std::optional getExtraData(Instruction::Type type, const cs_insn &instruction) override { + switch (type) { + using enum Instruction::Type; + + case Jump: { + // Get jump destination of jumps on ARM64 + if (instruction.id == ARM64_INS_B) { + if (instruction.detail->arm64.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->arm64.operands[0]; + + if (op.type == ARM64_OP_IMM) + return op.imm; + + if (op.type == ARM64_OP_MEM) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Call: { + if (instruction.id == ARM64_INS_BL) { + if (instruction.detail->arm64.op_count != 1) + return std::nullopt; + + const auto &op = instruction.detail->arm64.operands[0]; + + if (op.type == ARM64_OP_IMM) + return op.imm; + + if (op.type == ARM64_OP_MEM) + return instruction.address + instruction.size + op.mem.disp; + } + + break; + } + case Return: + break; + case Other: + break; + } + + return std::nullopt; } @@ -110,6 +351,8 @@ namespace hex::plugin::builtin { void registerDisassemblers() { ContentRegistry::Disassembler::add(); + ContentRegistry::Disassembler::add(); + ContentRegistry::Disassembler::add(); } } diff --git a/plugins/builtin/source/content/views/view_disassembler.cpp b/plugins/builtin/source/content/views/view_disassembler.cpp index 18a33c828..4827b3b7c 100644 --- a/plugins/builtin/source/content/views/view_disassembler.cpp +++ b/plugins/builtin/source/content/views/view_disassembler.cpp @@ -3,6 +3,7 @@ #include #include +#include using namespace std::literals::string_literals; @@ -13,19 +14,100 @@ namespace hex::plugin::builtin { } void ViewDisassembler::addLine(prv::Provider *provider, const ContentRegistry::Disassembler::Instruction &instruction) { - prv::ProviderReader reader(provider); - reader.seek(instruction.region.getStartAddress()); - reader.setEndAddress(instruction.region.getEndAddress()); + std::vector bytes(instruction.region.getSize()); + provider->read(instruction.region.getStartAddress(), bytes.data(), bytes.size()); - std::string bytes; - for (const auto& byte : reader) { - bytes += fmt::format("{:02X} ", byte); + std::string byteString; + for (const auto& byte : bytes) { + byteString += fmt::format("{:02X} ", byte); } - bytes.pop_back(); + byteString.pop_back(); - this->m_lines.get(provider).emplace_back(ImHexApi::HexEditor::ProviderRegion { instruction.region, provider }, std::move(bytes), instruction.mnemonic, instruction.operands, instruction.jumpDestination, ImVec2()); + switch (instruction.type) { + using enum ContentRegistry::Disassembler::Instruction::Type; + case Return: + this->m_lines.get(provider).emplace_back( + DisassemblyLine::Type::Instruction, + ImHexApi::HexEditor::ProviderRegion { + instruction.region, + provider + }, + std::move(byteString), + instruction.mnemonic, + instruction.operands, + instruction.extraData, + ImVec2() + ); + this->m_lines.get(provider).emplace_back(DisassemblyLine::Type::Separator); + break; + case Call: + this->m_lines.get(provider).emplace_back( + DisassemblyLine::Type::CallInstruction, + ImHexApi::HexEditor::ProviderRegion { + instruction.region, + provider + }, + std::move(byteString), + instruction.mnemonic, + instruction.operands, + instruction.extraData, + ImVec2() + ); + break; + case Jump: + case Other: + this->m_lines.get(provider).emplace_back( + DisassemblyLine::Type::Instruction, + ImHexApi::HexEditor::ProviderRegion { + instruction.region, + provider + }, + std::move(byteString), + instruction.mnemonic, + instruction.operands, + instruction.extraData, + ImVec2() + ); + break; + } } + bool ViewDisassembler::drawInstructionLine(DisassemblyLine& line) { + auto height = ImGui::GetTextLineHeight(); //ImGui::CalcTextSize(line.bytes.c_str(), nullptr, false, 80_scaled).y; + + ImGui::TableNextColumn(); + if (ImGui::Selectable(hex::format("0x{:08X}", line.region.getStartAddress()).c_str(), false, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, height))) { + ImHexApi::HexEditor::setSelection(line.region); + } + + bool hovered = ImGui::IsItemHovered(); + + ImGui::TableNextColumn(); + ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", line.bytes); + + ImGui::TableNextColumn(); + ImGuiExt::TextFormattedColored(ImGui::GetColorU32(ImGuiCol_HeaderActive), "{} ", line.mnemonic); + ImGui::SameLine(0, 0); + ImGuiExt::TextFormatted("{}", line.operands); + + return hovered; + } + + bool ViewDisassembler::drawSeparatorLine(DisassemblyLine&) { + ImGui::BeginDisabled(); + ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Selectable("##separator", true, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, 2_scaled)); + ImGui::PopStyleColor(); + ImGui::EndDisabled(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + return false; + } + + + static void drawJumpLine(ImVec2 start, ImVec2 end, float columnWidth, u32 slot, bool endVisible, bool hovered) { const u32 slotCount = std::floor(std::max(1.0F, columnWidth / 10_scaled)); @@ -84,136 +166,145 @@ namespace hex::plugin::builtin { ImGui::SameLine(); - if (this->m_lines->empty()) { + if (this->m_disassembleTask.isRunning() || this->m_lines->empty()) { + ImGui::BeginDisabled(this->m_disassembleTask.isRunning()); + auto provider = ImHexApi::Provider::get(); if (ImGuiExt::DimmedButton("Disassemble", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - auto disassembly = this->m_currArchitecture->disassemble(provider, ImHexApi::HexEditor::getSelection().value()); + this->m_disassembleTask = TaskManager::createTask("Disassembling...", this->m_disassembleRegion.getSize(), [this, provider](auto &task) { + const auto disassembly = this->m_currArchitecture->disassemble(provider, this->m_disassembleRegion, task); - for (const auto &instruction : disassembly) - this->addLine(provider, instruction); + task.setMaxValue(disassembly.size()); + for (const auto &[index, instruction] : disassembly | std::views::enumerate) { + task.update(index); + this->addLine(provider, instruction); + } + }); } ImGuiExt::BeginSubWindow("Config"); { + ui::regionSelectionPicker(&this->m_disassembleRegion, provider, &this->m_regionType, true, true); + + ImGuiExt::Header("Architecture Settings"); this->m_currArchitecture->drawConfigInterface(); } ImGuiExt::EndSubWindow(); + + ImGui::EndDisabled(); } else { if (ImGuiExt::DimmedButton("Reset", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { this->m_lines->clear(); } - } - if (ImGui::BeginTable("##disassembly", 4, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, ImGui::GetContentRegionAvail())) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("##jumps"); - ImGui::TableSetupColumn("##address", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, 80_scaled); - ImGui::TableSetupColumn("##bytes", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, 80_scaled); - ImGui::TableSetupColumn("##instruction", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoResize); + if (ImGui::BeginTable("##disassembly", 4, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, ImGui::GetContentRegionAvail())) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("##jumps"); + ImGui::TableSetupColumn("##address", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, 80_scaled); + ImGui::TableSetupColumn("##bytes", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, 120_scaled); + ImGui::TableSetupColumn("##instruction", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoResize); - ImGui::TableHeadersRow(); + ImGui::TableHeadersRow(); - ImGuiListClipper clipper; - clipper.Begin(this->m_lines->size(), ImGui::GetTextLineHeightWithSpacing()); + ImGuiListClipper clipper; + clipper.Begin(this->m_lines->size(), ImGui::GetTextLineHeightWithSpacing()); - int processingStart = 0, processingEnd = 0; + int processingStart = 0, processingEnd = 0; - float jumpColumnWidth = 0.0F; - std::optional hoveredAddress; - while (clipper.Step()) { - processingStart = clipper.DisplayStart; - processingEnd = clipper.DisplayEnd; - for (auto i = processingStart; i < processingEnd; i += 1) { - auto &line = this->m_lines->at(i); - ImGui::TableNextRow(); + float jumpColumnWidth = 0.0F; + std::optional hoveredAddress; + while (clipper.Step()) { + processingStart = clipper.DisplayStart; + processingEnd = clipper.DisplayEnd; + for (auto i = processingStart; i < processingEnd; i += 1) { + auto &line = this->m_lines->at(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); - auto height = ImGui::CalcTextSize(line.bytes.c_str(), nullptr, false, 80_scaled).y; + { + auto height = ImGui::CalcTextSize(line.bytes.c_str(), nullptr, false, 80_scaled).y; + // Reserve some space to draw the jump lines later - ImGui::TableNextColumn(); - { - // Reserve some space to draw the jump lines later - - // Remember the position of the line so we can draw the jump lines later - jumpColumnWidth = ImGui::GetContentRegionAvail().x; - line.linePos = ImGui::GetCursorScreenPos() + ImVec2(jumpColumnWidth, height / 2); - } - - ImGui::TableNextColumn(); - if (ImGui::Selectable(hex::format("0x{:08X}", line.region.getStartAddress()).c_str(), false, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, height))) { - ImHexApi::HexEditor::setSelection(line.region); - } - - if (ImGui::IsItemHovered()) - hoveredAddress = line.region.getStartAddress(); - - ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight)); - ImGuiExt::TextFormattedWrapped("{}", line.bytes); - ImGui::PopStyleColor(); - - ImGui::TableNextColumn(); - ImGuiExt::TextFormattedColored(ImGui::GetColorU32(ImGuiCol_HeaderActive), "{} ", line.mnemonic); - ImGui::SameLine(0, 0); - ImGuiExt::TextFormatted("{}", line.operands); - } - } - - // Draw jump arrows - if (!this->m_lines->empty()) { - auto &firstVisibleLine = this->m_lines->at(processingStart); - auto &lastVisibleLine = this->m_lines->at(processingEnd - 1); - - const u32 slotCount = std::floor(std::max(1.0F, jumpColumnWidth / 10_scaled)); - std::map occupiedSlots; - - auto findFreeSlot = [&](u64 jumpDestination) { - for (u32 i = 0; i < slotCount; i += 1) { - if (!occupiedSlots.contains(i) || occupiedSlots[i] == jumpDestination) { - return i; + // Remember the position of the line so we can draw the jump lines later + jumpColumnWidth = ImGui::GetContentRegionAvail().x; + line.linePosition = ImGui::GetCursorScreenPos() + ImVec2(jumpColumnWidth, height / 2); } - } - return slotCount; - }; - - for (auto sourceLineIndex = processingStart; sourceLineIndex < processingEnd; sourceLineIndex += 1) { - const auto &sourceLine = this->m_lines->at(sourceLineIndex); - - if (auto jumpDestination = sourceLine.jumpDestination; jumpDestination.has_value()) { - for (auto destinationLineIndex = processingStart; destinationLineIndex < processingEnd; destinationLineIndex += 1) { - const auto &destinationLine = this->m_lines->at(destinationLineIndex); - - auto freeSlot = findFreeSlot(*jumpDestination); - - bool jumpFound = false; - if (*jumpDestination == destinationLine.region.getStartAddress()) { - drawJumpLine(sourceLine.linePos, destinationLine.linePos, jumpColumnWidth, freeSlot, true, hoveredAddress == sourceLine.region.getStartAddress() || hoveredAddress == destinationLine.region.getStartAddress()); - jumpFound = true; - } else if (*jumpDestination > lastVisibleLine.region.getStartAddress()) { - drawJumpLine(sourceLine.linePos, lastVisibleLine.linePos, jumpColumnWidth, freeSlot, false, hoveredAddress == sourceLine.region.getStartAddress() || hoveredAddress == destinationLine.region.getStartAddress()); - jumpFound = true; - } else if (*jumpDestination < firstVisibleLine.region.getStartAddress()) { - drawJumpLine(sourceLine.linePos, firstVisibleLine.linePos, jumpColumnWidth, freeSlot, false, hoveredAddress == sourceLine.region.getStartAddress() || hoveredAddress == destinationLine.region.getStartAddress()); - jumpFound = true; - } - - if (jumpFound) { - if (!occupiedSlots.contains(freeSlot)) - occupiedSlots[freeSlot] = *jumpDestination; + switch (line.type) { + using enum DisassemblyLine::Type; + case CallInstruction: + case Instruction: + if (this->drawInstructionLine(line)) + hoveredAddress = line.region.getStartAddress(); break; + case Separator: + this->drawSeparatorLine(line); + break; + } + + } + } + + ImGui::EndTable(); + + // Draw jump arrows + if (!this->m_lines->empty()) { + auto &firstVisibleLine = this->m_lines->at(processingStart); + auto &lastVisibleLine = this->m_lines->at(processingEnd - 1); + + const u32 slotCount = std::floor(std::max(1.0F, jumpColumnWidth / 10_scaled)); + std::map occupiedSlots; + + auto findFreeSlot = [&](u64 jumpDestination) { + for (u32 i = 0; i < slotCount; i += 1) { + if (!occupiedSlots.contains(i) || occupiedSlots[i] == jumpDestination) { + return i; } } - } - std::erase_if(occupiedSlots, [&](const auto &entry) { - auto &[slot, destination] = entry; - return sourceLine.region.getStartAddress() == destination; - }); + return slotCount; + }; + + for (auto sourceLineIndex = processingStart; sourceLineIndex < processingEnd; sourceLineIndex += 1) { + const auto &sourceLine = this->m_lines->at(sourceLineIndex); + + if (auto jumpDestination = sourceLine.extraData; jumpDestination.has_value()) { + for (auto destinationLineIndex = processingStart; destinationLineIndex < processingEnd; destinationLineIndex += 1) { + const auto &destinationLine = this->m_lines->at(destinationLineIndex); + + const auto freeSlot = findFreeSlot(*jumpDestination); + const bool hovered = hoveredAddress == sourceLine.region.getStartAddress() || + hoveredAddress == destinationLine.region.getStartAddress(); + + bool jumpFound = false; + if (*jumpDestination == destinationLine.region.getStartAddress()) { + drawJumpLine(sourceLine.linePosition, destinationLine.linePosition, jumpColumnWidth, freeSlot, true, hovered); + jumpFound = true; + } else if (*jumpDestination > lastVisibleLine.region.getStartAddress()) { + drawJumpLine(sourceLine.linePosition, lastVisibleLine.linePosition, jumpColumnWidth, freeSlot, false, hovered); + jumpFound = true; + } else if (*jumpDestination < firstVisibleLine.region.getStartAddress()) { + drawJumpLine(sourceLine.linePosition, firstVisibleLine.linePosition, jumpColumnWidth, freeSlot, false, hovered); + jumpFound = true; + } + + if (jumpFound) { + if (!occupiedSlots.contains(freeSlot)) + occupiedSlots[freeSlot] = *jumpDestination; + break; + } + } + } + + std::erase_if(occupiedSlots, [&](const auto &entry) { + auto &[slot, destination] = entry; + return sourceLine.extraData.value() == destination; + }); + } } } - - ImGui::EndTable(); } + } }