13#include <TargetConditionals.h>
18#include "absl/strings/ascii.h"
19#include "absl/strings/match.h"
20#include "absl/strings/str_cat.h"
21#include "absl/strings/str_format.h"
22#include "absl/strings/str_join.h"
38#include "imgui/misc/cpp/imgui_stdlib.h"
45using util::FileDialogWrapper;
49static const char*
const kKeywords[] = {
50 "ADC",
"AND",
"ASL",
"BCC",
"BCS",
"BEQ",
"BIT",
"BMI",
"BNE",
"BPL",
51 "BRA",
"BRL",
"BVC",
"BVS",
"CLC",
"CLD",
"CLI",
"CLV",
"CMP",
"CPX",
52 "CPY",
"DEC",
"DEX",
"DEY",
"EOR",
"INC",
"INX",
"INY",
"JMP",
"JSR",
53 "JSL",
"LDA",
"LDX",
"LDY",
"LSR",
"MVN",
"NOP",
"ORA",
"PEA",
"PER",
54 "PHA",
"PHB",
"PHD",
"PHP",
"PHX",
"PHY",
"PLA",
"PLB",
"PLD",
"PLP",
55 "PLX",
"PLY",
"REP",
"ROL",
"ROR",
"RTI",
"RTL",
"RTS",
"SBC",
"SEC",
56 "SEI",
"SEP",
"STA",
"STP",
"STX",
"STY",
"STZ",
"TAX",
"TAY",
"TCD",
57 "TCS",
"TDC",
"TRB",
"TSB",
"TSC",
"TSX",
"TXA",
"TXS",
"TXY",
"TYA",
58 "TYX",
"WAI",
"WDM",
"XBA",
"XCE",
"ORG",
"LOROM",
"HIROM"};
60static const char*
const kIdentifiers[] = {
61 "abort",
"abs",
"acos",
"asin",
"atan",
"atexit",
62 "atof",
"atoi",
"atol",
"ceil",
"clock",
"cosh",
63 "ctime",
"div",
"exit",
"fabs",
"floor",
"fmod",
64 "getchar",
"getenv",
"isalnum",
"isalpha",
"isdigit",
"isgraph",
65 "ispunct",
"isspace",
"isupper",
"kbhit",
"log10",
"log2",
66 "log",
"memcmp",
"modf",
"pow",
"putchar",
"putenv",
67 "puts",
"rand",
"remove",
"rename",
"sinh",
"sqrt",
68 "srand",
"strcat",
"strcmp",
"strerror",
"time",
"tolower",
73 for (
auto& k : kKeywords)
76 for (
auto& k : kIdentifiers) {
79 language_65816.
mIdentifiers.insert(std::make_pair(std::string(k),
id));
83 std::make_pair<std::string, TextEditor::PaletteIndex>(
86 std::make_pair<std::string, TextEditor::PaletteIndex>(
89 std::make_pair<std::string, TextEditor::PaletteIndex>(
92 std::make_pair<std::string, TextEditor::PaletteIndex>(
93 "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
96 std::make_pair<std::string, TextEditor::PaletteIndex>(
99 std::make_pair<std::string, TextEditor::PaletteIndex>(
102 std::make_pair<std::string, TextEditor::PaletteIndex>(
103 "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
106 std::make_pair<std::string, TextEditor::PaletteIndex>(
109 std::make_pair<std::string, TextEditor::PaletteIndex>(
110 "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
121 language_65816.
mName =
"65816";
123 return language_65816;
127 return absl::StrContains(name,
'.');
131 return !name.empty() && name[0] ==
'.';
135 const std::vector<std::string>& ignored_files) {
136 return std::ranges::find(ignored_files, name) != ignored_files.end();
140 static const std::array<const char*, 13> kSkippedDirectories = {
141 ".git",
".context",
".idea",
".vscode",
"build",
142 "build_ai",
"build_agent",
"build_test",
"build-ios",
"build-ios-sim",
143 "build-wasm",
"node_modules",
"dist"};
144 for (
const char* skipped : kSkippedDirectories) {
145 if (name == skipped) {
156 std::string quoted =
"'";
157 for (
char ch : value) {
161 quoted.push_back(ch);
169 const std::filesystem::path& start,
const std::filesystem::path& relative) {
171 auto current = std::filesystem::absolute(start, ec);
175 while (!current.empty()) {
176 const auto candidate = current / relative;
177 if (std::filesystem::exists(candidate, ec) && !ec) {
180 if (current == current.root_path() || current == current.parent_path()) {
183 current = current.parent_path();
189 if (path.extension() !=
".asm") {
192 const std::string name = path.filename().string();
193 return absl::StartsWith(name,
"bank_");
200 std::ifstream file(path);
201 if (!file.is_open()) {
213 if ((address & 0xFF0000u) >= 0x800000u) {
214 address &= 0x7FFFFFu;
216 return static_cast<int>((address >> 16) & 0xFFu);
220 const std::string name = std::filesystem::path(path).filename().string();
221 if (!absl::StartsWith(name,
"bank_") || !absl::EndsWith(name,
".asm")) {
224 const std::string hex = name.substr(5, name.size() - 9);
226 return std::stoi(hex,
nullptr, 16);
237 std::string token = text;
238 const size_t first = token.find_first_not_of(
" \t");
239 if (first == std::string::npos) {
242 const size_t last = token.find_last_not_of(
" \t");
243 token = token.substr(first, last - first + 1);
247 if (token[0] ==
'$') {
248 token = token.substr(1);
249 }
else if (token.size() > 2 && token[0] ==
'0' &&
250 (token[1] ==
'x' || token[1] ==
'X')) {
251 token = token.substr(2);
254 return static_cast<uint32_t
>(std::stoul(token,
nullptr, 16));
264 std::sort(item->
files.begin(), item->
files.end());
267 return lhs.name < rhs.name;
275 std::vector<std::string> ignored_files;
276#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
277 std::ifstream gitignore(folder +
"/.gitignore");
278 if (gitignore.good()) {
280 while (std::getline(gitignore, line)) {
281 if (line.empty() || line[0] ==
'#' || line[0] ==
'!') {
284 ignored_files.push_back(line);
291 std::error_code path_ec;
292 std::filesystem::path root_path =
293 std::filesystem::weakly_canonical(folder, path_ec);
296 root_path = std::filesystem::absolute(folder, path_ec);
301 current_folder.
name = root_path.string();
303 std::error_code root_ec;
304 for (
const auto& entry :
305 std::filesystem::directory_iterator(root_path, root_ec)) {
310 const std::string entry_name = entry.path().filename().string();
315 std::error_code type_ec;
316 if (entry.is_regular_file(type_ec)) {
321 current_folder.
files.push_back(entry_name);
330 folder_item.
name = entry_name;
332 std::error_code sub_ec;
333 for (
const auto& sub_entry :
334 std::filesystem::directory_iterator(entry.path(), sub_ec)) {
339 const std::string sub_name = sub_entry.path().filename().string();
344 std::error_code sub_type_ec;
345 if (sub_entry.is_regular_file(sub_type_ec)) {
350 folder_item.
files.push_back(sub_name);
354 if (!sub_entry.is_directory(sub_type_ec) ||
360 subfolder_item.
name = sub_name;
361 std::error_code leaf_ec;
362 for (
const auto& leaf_entry :
363 std::filesystem::directory_iterator(sub_entry.path(), leaf_ec)) {
367 const std::string leaf_name = leaf_entry.path().filename().string();
371 std::error_code leaf_type_ec;
372 if (!leaf_entry.is_regular_file(leaf_type_ec)) {
379 subfolder_item.
files.push_back(leaf_name);
381 folder_item.
subfolders.push_back(std::move(subfolder_item));
384 current_folder.
subfolders.push_back(std::move(folder_item));
388 return current_folder;
392 const std::filesystem::path& path,
const std::string& label) {
393 std::ifstream file(path);
394 if (!file.is_open()) {
400 while (std::getline(file, line)) {
401 const size_t start = line.find_first_not_of(
" \t");
402 if (start == std::string::npos) {
407 if (line.compare(start, label.size(), label) != 0) {
412 size_t pos = start + label.size();
413 while (pos < line.size() && (line[pos] ==
' ' || line[pos] ==
'\t')) {
417 if (pos < line.size() && line[pos] ==
':') {
419 loc.
file = path.string();
420 loc.
line = line_index;
421 loc.
column =
static_cast<int>(start);
432 const auto ext = path.extension().string();
433 return ext ==
".asm" || ext ==
".inc" || ext ==
".s";
438 int line_one_based = 0;
439 int column_one_based = 1;
452 if (!std::isdigit(
static_cast<unsigned char>(c))) {
457 const int v = std::stoi(s);
458 return v > 0 ? std::optional<int>(v) : std::nullopt;
465 if (file_ref.empty()) {
468 const std::filesystem::path p(file_ref);
473 const std::string& reference) {
474 const std::string trimmed =
475 std::string(absl::StripAsciiWhitespace(reference));
476 if (trimmed.empty()) {
481 if (
const size_t pos = trimmed.find(
"#L"); pos != std::string::npos) {
482 const std::string file =
483 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, pos)));
484 const std::string line_str =
485 std::string(absl::StripAsciiWhitespace(trimmed.substr(pos + 2)));
498 const size_t last_colon = trimmed.rfind(
':');
499 if (last_colon == std::string::npos) {
503 const std::string tail =
504 std::string(absl::StripAsciiWhitespace(trimmed.substr(last_colon + 1)));
509 const size_t second_last_colon = (last_colon == 0)
511 : trimmed.rfind(
':', last_colon - 1);
513 if (second_last_colon != std::string::npos) {
514 const std::string file = std::string(
515 absl::StripAsciiWhitespace(trimmed.substr(0, second_last_colon)));
516 const std::string line_str =
517 std::string(absl::StripAsciiWhitespace(trimmed.substr(
518 second_last_colon + 1, last_colon - second_last_colon - 1)));
519 const std::string col_str = tail;
525 if (!line.has_value() || !col.has_value()) {
531 const std::string file =
532 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, last_colon)));
543 const std::string& reference) {
544 const std::string trimmed =
545 std::string(absl::StripAsciiWhitespace(reference));
546 if (trimmed.empty()) {
551 if (
const size_t pos = trimmed.rfind(
'#'); pos != std::string::npos) {
552 const std::string file =
553 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, pos)));
554 const std::string sym =
555 std::string(absl::StripAsciiWhitespace(trimmed.substr(pos + 1)));
563 const size_t last_colon = trimmed.rfind(
':');
564 if (last_colon == std::string::npos) {
568 const std::string file =
569 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, last_colon)));
570 const std::string sym =
571 std::string(absl::StripAsciiWhitespace(trimmed.substr(last_colon + 1)));
586 const std::filesystem::path& root,
const std::string& file_ref) {
587 std::filesystem::path p(file_ref);
588 if (p.is_absolute()) {
590 if (std::filesystem::exists(p, ec) &&
591 std::filesystem::is_regular_file(p, ec)) {
599 const std::filesystem::path candidate = root / p;
601 if (std::filesystem::exists(candidate, ec) &&
602 std::filesystem::is_regular_file(candidate, ec)) {
608 const std::string want_suffix = p.generic_string();
609 const std::string want_name = p.filename().string();
612 if (!std::filesystem::exists(root, ec)) {
616 std::filesystem::recursive_directory_iterator it(
617 root, std::filesystem::directory_options::skip_permission_denied, ec);
618 const std::filesystem::recursive_directory_iterator end;
619 for (; it != end && !ec; it.increment(ec)) {
620 const auto& entry = *it;
621 if (entry.is_directory()) {
622 const auto name = entry.path().filename().string();
623 if (!name.empty() && name.front() ==
'.') {
624 it.disable_recursion_pending();
626 it.disable_recursion_pending();
631 if (!entry.is_regular_file()) {
638 const std::string cand = entry.path().generic_string();
639 if (!want_suffix.empty() && absl::EndsWith(cand, want_suffix)) {
642 if (!want_name.empty() && entry.path().filename() == want_name) {
651 const std::filesystem::path& root,
const std::string& label) {
653 if (!std::filesystem::exists(root, ec)) {
657 std::filesystem::recursive_directory_iterator it(
658 root, std::filesystem::directory_options::skip_permission_denied, ec);
659 const std::filesystem::recursive_directory_iterator end;
660 for (; it != end && !ec; it.increment(ec)) {
661 const auto& entry = *it;
662 if (entry.is_directory()) {
663 const auto name = entry.path().filename().string();
664 if (!name.empty() && name.front() ==
'.') {
665 it.disable_recursion_pending();
667 it.disable_recursion_pending();
672 if (!entry.is_regular_file()) {
680 if (
auto loc =
FindLabelInFile(entry.path(), label); loc.has_value()) {
700 std::make_unique<AssemblyCodeEditorPanel>(
704 window_manager->RegisterWindowContent(
705 std::make_unique<AssemblyFileBrowserPanel>(
709 window_manager->RegisterWindowContent(std::make_unique<AssemblySymbolsPanel>(
713 window_manager->RegisterWindowContent(
714 std::make_unique<AssemblyBuildOutputPanel>(
717 window_manager->RegisterWindowContent(
718 std::make_unique<AssemblyDisassemblyPanel>(
722 window_manager->RegisterWindowContent(std::make_unique<AssemblyToolbarPanel>(
728 return absl::OkStatus();
732 if (symbol.empty()) {
733 return absl::InvalidArgumentError(
"Symbol is empty");
736 std::filesystem::path root;
743 return absl::FailedPreconditionError(
744 "No code folder loaded (open a folder or set project code_folder)");
747 const std::string root_string = root.string();
760 const auto& cached = it->second;
763 return absl::InternalError(
"Failed to open file for symbol: " + symbol);
768 return absl::InternalError(
"No active text editor");
771 editor->SetCursorPosition(
773 editor->SelectWordUnderCursor();
774 return absl::OkStatus();
778 return absl::NotFoundError(
"Symbol not found: " + symbol);
781 const auto loc = FindLabelInFolder(root, symbol);
782 if (!loc.has_value()) {
784 return absl::NotFoundError(
"Symbol not found: " + symbol);
792 return absl::InternalError(
"Failed to open file for symbol: " + symbol);
797 return absl::InternalError(
"No active text editor");
801 editor->SelectWordUnderCursor();
802 return absl::OkStatus();
806 if (reference.empty()) {
807 return absl::InvalidArgumentError(
"Reference is empty");
810 if (
auto file_ref = ParseAsmFileLineRef(reference); file_ref.has_value()) {
811 std::filesystem::path root;
818 return absl::FailedPreconditionError(
819 "No code folder loaded (open a folder or set project code_folder)");
826 auto path_or = FindAsmFileInFolder(root, file_ref->file_ref);
827 if (!path_or.has_value()) {
828 return absl::NotFoundError(
"File not found: " + file_ref->file_ref);
833 return absl::InternalError(
"Failed to open file: " + path_or->string());
836 const int line0 = std::max(0, file_ref->line_one_based - 1);
837 const int col0 = std::max(0, file_ref->column_one_based - 1);
840 return absl::InternalError(
"No active text editor");
843 editor->SelectWordUnderCursor();
844 return absl::OkStatus();
847 if (
auto file_ref = ParseAsmFileSymbolRef(reference); file_ref.has_value()) {
848 std::filesystem::path root;
855 return absl::FailedPreconditionError(
856 "No code folder loaded (open a folder or set project code_folder)");
863 auto path_or = FindAsmFileInFolder(root, file_ref->file_ref);
864 if (!path_or.has_value()) {
865 return absl::NotFoundError(
"File not found: " + file_ref->file_ref);
868 auto loc = FindLabelInFile(*path_or, file_ref->symbol);
869 if (!loc.has_value()) {
870 return absl::NotFoundError(absl::StrCat(
871 "Symbol not found in ", file_ref->file_ref,
": ", file_ref->symbol));
876 return absl::InternalError(
"Failed to open file: " + loc->file);
881 return absl::InternalError(
"No active text editor");
884 editor->SelectWordUnderCursor();
885 return absl::OkStatus();
940 if (ImGui::BeginMenuBar()) {
949 const char* file_label =
951 ImGui::Text(
"%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
958 editor->
Render(
"##asm_editor",
959 ImVec2(0, -ImGui::GetFrameHeightWithSpacing()));
976 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
980 ImGui::TextDisabled(
"No folder opened");
985 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
"%s",
995 ImGui::TextDisabled(
"No symbols loaded.");
998 "Apply a patch or load external symbols to populate this list.");
1003 static char filter[256] =
"";
1004 ImGui::SetNextItemWidth(-1);
1005 ImGui::InputTextWithHint(
"##symbol_filter",
1011 if (ImGui::BeginChild(
"##symbol_list", ImVec2(0, 0),
false)) {
1012 for (
const auto& [name, symbol] :
symbols_) {
1014 if (filter[0] !=
'\0' && name.find(filter) == std::string::npos) {
1018 ImGui::PushID(name.c_str());
1019 if (ImGui::Selectable(name.c_str())) {
1022 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
1023 ImGui::TextDisabled(
"$%06X", symbol.address);
1032 ImGui::Text(
"Errors: %zu Warnings: %zu",
last_errors_.size(),
1041 if (has_active_file) {
1050 bool apply_disabled = !has_rom || !has_active_file;
1051 ImGui::BeginDisabled(apply_disabled);
1052 if (ImGui::Button(
ICON_MD_BUILD " Apply to ROM", ImVec2(140, 0))) {
1058 ImGui::EndDisabled();
1059 if (apply_disabled &&
1060 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1062 ImGui::SetTooltip(
"Load a ROM first");
1064 ImGui::SetTooltip(
"Open an assembly file first");
1070 if (ImGui::BeginChild(
"##build_log", ImVec2(0, 0),
true)) {
1076 int line,
int column) {
1077 if (!file.empty()) {
1078 std::string reference = file;
1080 reference = absl::StrCat(reference,
":", line);
1082 reference = absl::StrCat(reference,
":", column);
1088 "Failed to open diagnostic location: " +
1089 std::string(status.message()),
1097 column > 0 ? column - 1 : 0);
1098 editor->SetCursorPosition(coords);
1106 ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
1111 ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
1115 ImGui::TextDisabled(
"No build output");
1125 return (symbol_it->second.address >> 16) & 0xFFu;
1128 if (!parsed.has_value()) {
1129 return std::nullopt;
1131 return (*parsed >> 16) & 0xFFu;
1135 if (
const char* env = std::getenv(
"Z3DISASM_BIN"); env && env[0] !=
'\0') {
1136 return std::string(env);
1140 const auto cwd = std::filesystem::current_path(ec);
1143 FindUpwardPath(cwd, std::filesystem::path(
"scripts") /
"z3disasm");
1144 script.has_value()) {
1145 return script->string();
1160 return (std::filesystem::path(
rom_->
filename()).parent_path() /
"z3disasm")
1164 return (std::filesystem::current_path(ec) /
"z3disasm").string();
1193 return absl::StrFormat(
"project-graph --query=bank --bank=%02X --format=json",
1198 uint32_t address)
const {
1199 return absl::StrFormat(
1200 "project-graph --query=lookup --address=%06X --format=json", address);
1204 const std::vector<std::string>& args,
const std::string& title) {
1206 if (!editor_manager || !editor_manager->right_drawer_manager()) {
1207 return absl::FailedPreconditionError(
1208 "Right drawer manager is unavailable for project-graph results");
1219 auto status = tool.
Run(args,
nullptr, &output);
1234 {
"--query=lookup", absl::StrFormat(
"--address=%06X", address),
1236 absl::StrFormat(
"Project Graph Lookup $%06X", address))
1240 editor_manager->right_drawer_manager()->SetToolOutput(
1241 title, absl::StrJoin(args,
" "), output, std::move(actions));
1242 editor_manager->right_drawer_manager()->OpenDrawer(
1244 return absl::OkStatus();
1256 if (selected_bank < 0) {
1262 const std::string sourcemap_path =
1263 !project.z3dk_settings.artifact_paths.sourcemap_json.
empty()
1264 ? project.z3dk_settings.artifact_paths.sourcemap_json
1265 : project.GetZ3dkArtifactPath(
"sourcemap.json");
1266 if (LoadJsonFile(sourcemap_path, &sourcemap) &&
1269 std::map<int, std::string> files_by_id;
1270 for (
const auto& file : sourcemap[
"files"]) {
1271 files_by_id[file.value(
"id", -1)] = file.value(
"path",
"");
1274 for (
const auto& entry : sourcemap[
"entries"]) {
1275 const std::string address_str = entry.value(
"address",
"0x0");
1276 auto address = ParseHexAddress(address_str);
1277 if (!address.has_value() ||
1278 NormalizeLoRomBankIndex(*address) != selected_bank) {
1284 jump.
line = entry.value(
"line", 0);
1285 jump.
file = files_by_id[entry.value(
"file_id", -1)];
1286 if (!jump.
file.empty()) {
1292 if (lhs.address != rhs.address) {
1293 return lhs.address < rhs.address;
1296 return lhs.file < rhs.file;
1303 const std::string hooks_path =
1304 !project.z3dk_settings.artifact_paths.hooks_json.
empty()
1305 ? project.z3dk_settings.artifact_paths.hooks_json
1306 : project.GetZ3dkArtifactPath(
"hooks.json");
1307 if (LoadJsonFile(hooks_path, &hooks) && hooks.
contains(
"hooks") &&
1309 for (
const auto& hook : hooks[
"hooks"]) {
1310 const std::string address_str = hook.value(
"address",
"0x0");
1311 auto address = ParseHexAddress(address_str);
1312 if (!address.has_value() ||
1313 NormalizeLoRomBankIndex(*address) != selected_bank) {
1317 Z3DisasmHookJump jump;
1318 jump.address = *address;
1319 jump.size = hook.value(
"size", 0);
1320 jump.kind = hook.value(
"kind",
"patch");
1321 jump.name = hook.value(
"name",
"");
1322 jump.source = hook.value(
"source",
"");
1323 z3disasm_hook_jumps_.push_back(std::move(jump));
1325 std::sort(z3disasm_hook_jumps_.begin(), z3disasm_hook_jumps_.end(),
1326 [](
const Z3DisasmHookJump& lhs,
const Z3DisasmHookJump& rhs) {
1327 if (lhs.address != rhs.address) {
1328 return lhs.address < rhs.address;
1330 return lhs.name < rhs.name;
1335void AssemblyEditor::LoadSelectedZ3DisassemblyFile() {
1336 z3disasm_selected_contents_.clear();
1337 z3disasm_selected_path_.clear();
1338 z3disasm_source_jumps_.clear();
1339 z3disasm_hook_jumps_.clear();
1340 if (z3disasm_selected_index_ < 0 ||
1341 z3disasm_selected_index_ >=
static_cast<int>(z3disasm_files_.size())) {
1345 z3disasm_selected_path_ = z3disasm_files_[z3disasm_selected_index_];
1346 std::ifstream file(z3disasm_selected_path_);
1347 if (!file.is_open()) {
1348 z3disasm_status_ = absl::StrCat(
"Failed to open ", z3disasm_selected_path_);
1351 z3disasm_selected_contents_.assign(std::istreambuf_iterator<char>(file),
1352 std::istreambuf_iterator<char>());
1353 RefreshSelectedZ3DisassemblyMetadata();
1356void AssemblyEditor::RefreshZ3DisassemblyFiles() {
1357 z3disasm_files_.clear();
1358 const std::filesystem::path output_dir(ResolveZ3DisasmOutputDir());
1360 if (!std::filesystem::exists(output_dir, ec) || ec) {
1361 z3disasm_selected_index_ = -1;
1362 z3disasm_selected_path_.clear();
1363 z3disasm_selected_contents_.clear();
1364 z3disasm_source_jumps_.clear();
1365 z3disasm_hook_jumps_.clear();
1369 std::string previous_selection = z3disasm_selected_path_;
1370 for (
const auto& entry :
1371 std::filesystem::directory_iterator(output_dir, ec)) {
1375 if (!entry.is_regular_file()) {
1378 if (IsGeneratedBankFile(entry.path())) {
1379 z3disasm_files_.push_back(entry.path().string());
1382 std::sort(z3disasm_files_.begin(), z3disasm_files_.end());
1384 if (z3disasm_files_.empty()) {
1385 z3disasm_selected_index_ = -1;
1386 z3disasm_selected_path_.clear();
1387 z3disasm_selected_contents_.clear();
1388 z3disasm_source_jumps_.clear();
1389 z3disasm_hook_jumps_.clear();
1393 z3disasm_selected_index_ = 0;
1394 if (!previous_selection.empty()) {
1395 const auto it = std::find(z3disasm_files_.begin(), z3disasm_files_.end(),
1396 previous_selection);
1397 if (it != z3disasm_files_.end()) {
1398 z3disasm_selected_index_ =
1399 static_cast<int>(std::distance(z3disasm_files_.begin(), it));
1402 LoadSelectedZ3DisassemblyFile();
1405void AssemblyEditor::PollZ3DisassemblyTask() {
1406 const auto snapshot = z3disasm_task_.GetSnapshot();
1407 if (!snapshot.started) {
1410 if (snapshot.running) {
1412 absl::StrCat(
"z3disasm running...\n", snapshot.output_tail);
1415 if (z3disasm_task_acknowledged_ || !snapshot.finished) {
1419 z3disasm_task_acknowledged_ =
true;
1420 if (snapshot.status.ok()) {
1421 RefreshZ3DisassemblyFiles();
1422 z3disasm_status_ = absl::StrCat(
1423 "z3disasm finished.",
1424 z3disasm_files_.empty()
1425 ? std::string(
" No bank files were generated.")
1426 : absl::StrFormat(
" Loaded %d bank file(s).",
1427 static_cast<int>(z3disasm_files_.size())));
1430 absl::StrCat(
"z3disasm failed: ", snapshot.status.message(),
"\n",
1431 snapshot.output_tail);
1433 z3disasm_task_.Wait().IgnoreError();
1436absl::Status AssemblyEditor::GenerateZ3Disassembly() {
1437 if (
const auto snapshot = z3disasm_task_.GetSnapshot(); snapshot.running) {
1438 return absl::FailedPreconditionError(
"z3disasm is already running");
1441 const std::string rom_path = ResolveZ3DisasmRomPath();
1442 if (rom_path.empty()) {
1443 return absl::FailedPreconditionError(
1444 "No ROM path is available for z3disasm");
1447 const std::string command_path = ResolveZ3DisasmCommand();
1448 const std::string output_dir = ResolveZ3DisasmOutputDir();
1449 z3disasm_output_dir_ = output_dir;
1452 std::filesystem::create_directories(output_dir, ec);
1453 for (
const auto& entry :
1454 std::filesystem::directory_iterator(output_dir, ec)) {
1455 if (!ec && entry.is_regular_file() && IsGeneratedBankFile(entry.path())) {
1456 std::filesystem::remove(entry.path(), ec);
1460 std::string command = ShellQuote(command_path);
1461 command +=
" --rom ";
1462 command += ShellQuote(rom_path);
1463 command +=
" --out ";
1464 command += ShellQuote(output_dir);
1466 if (!z3disasm_all_banks_) {
1467 command += absl::StrFormat(
" --bank-start %02X --bank-end %02X",
1468 std::clamp(z3disasm_bank_start_, 0, 0xFF),
1469 std::clamp(z3disasm_bank_end_, 0, 0xFF));
1472 if (dependencies_.project) {
1473 const std::string symbols_path =
1474 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb.empty()
1475 ? dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
1476 : dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
1477 if (!symbols_path.empty() && std::filesystem::exists(symbols_path)) {
1478 command +=
" --symbols ";
1479 command += ShellQuote(symbols_path);
1482 const std::string hooks_path =
1483 !dependencies_.project->z3dk_settings.artifact_paths.hooks_json.empty()
1484 ? dependencies_.project->z3dk_settings.artifact_paths.hooks_json
1485 : dependencies_.project->GetZ3dkArtifactPath(
"hooks.json");
1486 if (!hooks_path.empty() && std::filesystem::exists(hooks_path)) {
1487 command +=
" --hooks ";
1488 command += ShellQuote(hooks_path);
1492 z3disasm_task_acknowledged_ =
false;
1493 z3disasm_selected_index_ = -1;
1494 z3disasm_selected_path_.clear();
1495 z3disasm_selected_contents_.clear();
1496 z3disasm_source_jumps_.clear();
1497 z3disasm_hook_jumps_.clear();
1498 z3disasm_files_.clear();
1499 z3disasm_status_ =
"Launching z3disasm...";
1500 return z3disasm_task_.Start(command,
1501 std::filesystem::current_path().
string());
1504absl::Status AssemblyEditor::NavigateDisassemblyQuery() {
1505 auto symbol_it = symbols_.find(disasm_query_);
1506 if (symbol_it != symbols_.end()) {
1507 disasm_query_ = absl::StrCat(
"0x", absl::Hex(symbol_it->second.address));
1508 disasm_status_ = absl::StrCat(
"Resolved symbol ", symbol_it->first);
1509 return absl::OkStatus();
1512 auto parsed = ParseHexAddress(disasm_query_);
1513 if (!parsed.has_value()) {
1514 return absl::InvalidArgumentError(
"Enter a SNES address or known symbol");
1517 disasm_query_ = absl::StrCat(
"0x", absl::Hex(*parsed));
1518 disasm_status_ = absl::StrFormat(
"Showing disassembly at $%06X", *parsed);
1519 return absl::OkStatus();
1522void AssemblyEditor::DrawDisassemblyContent() {
1523 PollZ3DisassemblyTask();
1525 if (z3disasm_output_dir_.empty()) {
1526 z3disasm_output_dir_ = ResolveZ3DisasmOutputDir();
1528 if (z3disasm_files_.empty()) {
1529 RefreshZ3DisassemblyFiles();
1532 ImGui::TextDisabled(
1533 "z3disasm-backed bank browser for generated `bank_XX.asm` files.");
1534 const std::string rom_path = ResolveZ3DisasmRomPath();
1535 ImGui::TextWrapped(
"ROM: %s",
1536 rom_path.empty() ?
"<unavailable>" : rom_path.c_str());
1537 ImGui::TextWrapped(
"Output: %s", z3disasm_output_dir_.c_str());
1539 ImGui::Checkbox(
"All banks", &z3disasm_all_banks_);
1540 if (!z3disasm_all_banks_) {
1542 ImGui::SetNextItemWidth(70.0f);
1543 ImGui::InputInt(
"Start", &z3disasm_bank_start_);
1545 ImGui::SetNextItemWidth(70.0f);
1546 ImGui::InputInt(
"End", &z3disasm_bank_end_);
1547 z3disasm_bank_start_ = std::clamp(z3disasm_bank_start_, 0, 0xFF);
1548 z3disasm_bank_end_ = std::clamp(z3disasm_bank_end_, 0, 0xFF);
1549 if (z3disasm_bank_end_ < z3disasm_bank_start_) {
1550 z3disasm_bank_end_ = z3disasm_bank_start_;
1553 if (ImGui::SmallButton(
"Use Query Bank")) {
1554 if (
auto bank = CurrentDisassemblyBank(); bank.has_value()) {
1555 z3disasm_bank_start_ =
static_cast<int>(*bank);
1556 z3disasm_bank_end_ =
static_cast<int>(*bank);
1561 const auto task_snapshot = z3disasm_task_.GetSnapshot();
1562 const bool can_generate = !rom_path.empty();
1563 ImGui::BeginDisabled(!can_generate || task_snapshot.running);
1565 auto status = GenerateZ3Disassembly();
1567 z3disasm_status_ = std::string(status.message());
1568 if (dependencies_.toast_manager) {
1569 dependencies_.toast_manager->Show(z3disasm_status_, ToastType::kError);
1573 ImGui::EndDisabled();
1574 if (task_snapshot.running) {
1577 z3disasm_task_.Cancel();
1580 if (!can_generate) {
1582 ImGui::TextDisabled(
"Load or configure a ROM path first.");
1585 if (!z3disasm_status_.empty()) {
1587 ImGui::TextWrapped(
"%s", z3disasm_status_.c_str());
1590 if (!task_snapshot.output_tail.empty()) {
1591 if (ImGui::CollapsingHeader(
"z3disasm Output")) {
1592 std::string output_tail = task_snapshot.output_tail;
1593 ImGui::InputTextMultiline(
"##z3disasm_output", &output_tail,
1594 ImVec2(-1.0f, 80.0f),
1595 ImGuiInputTextFlags_ReadOnly);
1599 ImGui::SeparatorText(
"Bank Browser");
1600 if (z3disasm_files_.empty()) {
1601 ImGui::TextDisabled(
1602 "No generated bank files yet. Run z3disasm to populate this browser.");
1604 if (ImGui::BeginTable(
1605 "##z3disasm_browser", 2,
1606 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
1607 ImGui::TableSetupColumn(
"Banks", ImGuiTableColumnFlags_WidthFixed,
1609 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch);
1610 ImGui::TableNextRow();
1612 ImGui::TableSetColumnIndex(0);
1613 if (ImGui::BeginChild(
"##z3disasm_list", ImVec2(0, 0),
true)) {
1614 for (
int i = 0; i < static_cast<int>(z3disasm_files_.size()); ++i) {
1615 const std::string name =
1616 std::filesystem::path(z3disasm_files_[i]).filename().string();
1617 if (ImGui::Selectable(name.c_str(), z3disasm_selected_index_ == i)) {
1618 z3disasm_selected_index_ = i;
1619 LoadSelectedZ3DisassemblyFile();
1625 ImGui::TableSetColumnIndex(1);
1626 if (ImGui::BeginChild(
"##z3disasm_preview", ImVec2(0, 0),
true)) {
1627 if (!z3disasm_selected_path_.empty()) {
1628 const std::string selected_name =
1629 std::filesystem::path(z3disasm_selected_path_)
1632 ImGui::TextUnformatted(selected_name.c_str());
1634 if (ImGui::SmallButton(
"Open in Code Editor")) {
1635 ChangeActiveFile(z3disasm_selected_path_);
1637 const std::string bank_query = BuildProjectGraphBankQuery();
1638 if (!bank_query.empty()) {
1640 if (ImGui::SmallButton(
"Open Bank Graph")) {
1641 auto status = RunProjectGraphQueryInDrawer(
1643 absl::StrFormat(
"--bank=%02X", SelectedZ3DisasmBankIndex()),
1645 absl::StrFormat(
"Project Graph Bank $%02X",
1646 SelectedZ3DisasmBankIndex()));
1648 z3disasm_status_ = std::string(status.message());
1652 if (ImGui::SmallButton(
"Copy Bank Query")) {
1653 ImGui::SetClipboardText(bank_query.c_str());
1655 ImGui::TextDisabled(
"%s", bank_query.c_str());
1658 if (!z3disasm_source_jumps_.empty() &&
1659 ImGui::CollapsingHeader(
"Source Map Jumps",
1660 ImGuiTreeNodeFlags_DefaultOpen)) {
1661 for (
const auto& jump : z3disasm_source_jumps_) {
1662 ImGui::PushID(
static_cast<int>(jump.address) ^ jump.line);
1663 const std::string source_label =
1664 absl::StrFormat(
"Source##source_jump_%06X", jump.address);
1665 const std::string graph_label =
1666 absl::StrFormat(
"Graph##source_graph_%06X", jump.address);
1667 const std::string copy_label =
1668 absl::StrFormat(
"Copy##source_copy_%06X", jump.address);
1669 const std::string addr_label =
1670 absl::StrFormat(
"Addr##source_addr_%06X", jump.address);
1671 if (ImGui::SmallButton(source_label.c_str())) {
1672 JumpToReference(absl::StrCat(jump.file,
":", jump.line))
1676 if (ImGui::SmallButton(graph_label.c_str())) {
1677 auto status = RunProjectGraphQueryInDrawer(
1679 absl::StrFormat(
"--address=%06X", jump.address),
1681 absl::StrFormat(
"Project Graph Lookup $%06X",
1684 z3disasm_status_ = std::string(status.message());
1688 if (ImGui::SmallButton(copy_label.c_str())) {
1689 const std::string query =
1690 BuildProjectGraphLookupQuery(jump.address);
1691 ImGui::SetClipboardText(query.c_str());
1694 if (ImGui::SmallButton(addr_label.c_str())) {
1695 disasm_query_ = absl::StrFormat(
"0x%06X", jump.address);
1696 NavigateDisassemblyQuery().IgnoreError();
1699 ImGui::TextWrapped(
"$%06X %s:%d", jump.address,
1700 jump.file.c_str(), jump.line);
1704 if (!z3disasm_hook_jumps_.empty() &&
1705 ImGui::CollapsingHeader(
"Hook Jumps",
1706 ImGuiTreeNodeFlags_DefaultOpen)) {
1707 for (
const auto& hook : z3disasm_hook_jumps_) {
1708 ImGui::PushID(
static_cast<int>(hook.address) ^ hook.size ^
1710 const std::string source_label =
1711 absl::StrFormat(
"Source##hook_jump_%06X", hook.address);
1712 const std::string graph_label =
1713 absl::StrFormat(
"Graph##hook_graph_%06X", hook.address);
1714 const std::string copy_label =
1715 absl::StrFormat(
"Copy##hook_copy_%06X", hook.address);
1716 const std::string addr_label =
1717 absl::StrFormat(
"Addr##hook_addr_%06X", hook.address);
1718 if (!hook.source.empty() &&
1719 ImGui::SmallButton(source_label.c_str())) {
1720 JumpToReference(hook.source).IgnoreError();
1722 if (!hook.source.empty()) {
1725 if (ImGui::SmallButton(graph_label.c_str())) {
1726 auto status = RunProjectGraphQueryInDrawer(
1728 absl::StrFormat(
"--address=%06X", hook.address),
1730 absl::StrFormat(
"Project Graph Lookup $%06X",
1733 z3disasm_status_ = std::string(status.message());
1737 if (ImGui::SmallButton(copy_label.c_str())) {
1738 const std::string query =
1739 BuildProjectGraphLookupQuery(hook.address);
1740 ImGui::SetClipboardText(query.c_str());
1743 if (ImGui::SmallButton(addr_label.c_str())) {
1744 disasm_query_ = absl::StrFormat(
"0x%06X", hook.address);
1745 NavigateDisassemblyQuery().IgnoreError();
1748 ImGui::TextWrapped(
"$%06X %s %s (+%d) %s", hook.address,
1749 hook.kind.c_str(), hook.name.c_str(),
1750 hook.size, hook.source.c_str());
1755 ImGui::InputTextMultiline(
1756 "##z3disasm_text", &z3disasm_selected_contents_,
1757 ImVec2(-1.0f, -1.0f), ImGuiInputTextFlags_ReadOnly);
1759 ImGui::TextDisabled(
"Select a generated bank file to preview it.");
1767 ImGui::SeparatorText(
"Quick ROM Slice");
1768 ImGui::TextDisabled(
"Inline viewer for the currently loaded ROM buffer.");
1769 ImGui::SetNextItemWidth(220.0f);
1770 ImGui::InputText(
"Address or Symbol", &disasm_query_);
1772 if (ImGui::Button(
"Go")) {
1773 auto status = NavigateDisassemblyQuery();
1775 disasm_status_ = std::string(status.message());
1779 ImGui::SetNextItemWidth(80.0f);
1780 ImGui::InputInt(
"Count", &disasm_instruction_count_);
1781 if (disasm_instruction_count_ < 1) {
1782 disasm_instruction_count_ = 1;
1784 if (disasm_instruction_count_ > 128) {
1785 disasm_instruction_count_ = 128;
1788 if (!disasm_status_.empty()) {
1789 ImGui::TextDisabled(
"%s", disasm_status_.c_str());
1793 if (!rom_ || !rom_->is_loaded()) {
1794 ImGui::TextDisabled(
"Load a ROM to browse disassembly.");
1798 uint32_t start_address = 0;
1799 if (
auto symbol_it = symbols_.find(disasm_query_);
1800 symbol_it != symbols_.end()) {
1801 start_address = symbol_it->second.address;
1803 auto parsed = ParseHexAddress(disasm_query_);
1804 if (!parsed.has_value()) {
1805 ImGui::TextDisabled(
1806 "Enter a SNES address like 0x008000 or a known label.");
1809 start_address = *parsed;
1812 std::map<uint32_t, std::string> symbols_by_address;
1813 for (
const auto& [name, symbol] : symbols_) {
1814 symbols_by_address.emplace(symbol.address, name);
1819 auto it = symbols_by_address.find(address);
1820 return it == symbols_by_address.end() ? std::string() : it->second;
1823 auto read_byte = [
this](uint32_t snes_addr) -> uint8_t {
1824 if (!rom_ || !rom_->is_loaded()) {
1827 const uint32_t pc_addr =
SnesToPc(snes_addr);
1828 if (pc_addr >= rom_->size()) {
1831 return rom_->vector()[pc_addr];
1835 start_address,
static_cast<size_t>(disasm_instruction_count_), read_byte);
1836 if (instructions.empty()) {
1837 ImGui::TextDisabled(
"No disassembly available for this address.");
1841 if (ImGui::BeginTable(
"##assembly_disasm", 3,
1842 ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
1843 ImGuiTableFlags_Resizable)) {
1844 ImGui::TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed,
1846 ImGui::TableSetupColumn(
"Instruction", ImGuiTableColumnFlags_WidthStretch);
1847 ImGui::TableSetupColumn(
"Context", ImGuiTableColumnFlags_WidthFixed,
1849 ImGui::TableHeadersRow();
1851 for (
const auto& instruction : instructions) {
1852 ImGui::PushID(
static_cast<int>(instruction.address));
1853 ImGui::TableNextRow();
1855 ImGui::TableSetColumnIndex(0);
1856 const std::string address_label =
1857 absl::StrFormat(
"$%06X", instruction.address);
1858 ImGui::TextUnformatted(address_label.c_str());
1860 ImGui::TableSetColumnIndex(1);
1861 if (
auto label_it = symbols_by_address.find(instruction.address);
1862 label_it != symbols_by_address.end()) {
1863 ImGui::TextColored(ImVec4(0.75f, 0.85f, 1.0f, 1.0f),
1864 "%s:", label_it->second.c_str());
1866 ImGui::TextWrapped(
"%s", instruction.full_text.c_str());
1868 ImGui::TableSetColumnIndex(2);
1869 if (instruction.branch_target != 0) {
1870 auto target_it = symbols_by_address.find(instruction.branch_target);
1871 if (target_it != symbols_by_address.end()) {
1872 if (ImGui::SmallButton(target_it->second.c_str())) {
1873 JumpToSymbolDefinition(target_it->second).IgnoreError();
1876 ImGui::Text(
"$%06X", instruction.branch_target);
1879 ImGui::TextDisabled(
"-");
1887void AssemblyEditor::DrawToolbarContent() {
1888 float button_size = 32.0f;
1891 auto folder = FileDialogWrapper::ShowOpenFolderDialog();
1892 if (!folder.empty()) {
1893 current_folder_ = LoadFolder(folder);
1896 if (ImGui::IsItemHovered())
1897 ImGui::SetTooltip(
"Open Folder");
1901 auto filename = FileDialogWrapper::ShowOpenFileDialog();
1902 if (!filename.empty()) {
1903 ChangeActiveFile(filename);
1906 if (ImGui::IsItemHovered())
1907 ImGui::SetTooltip(
"Open File");
1910 bool can_save = HasActiveFile();
1911 ImGui::BeginDisabled(!can_save);
1912 if (ImGui::Button(
ICON_MD_SAVE, ImVec2(button_size, button_size))) {
1915 ImGui::EndDisabled();
1916 if (ImGui::IsItemHovered())
1917 ImGui::SetTooltip(
"Save File");
1924 ImGui::BeginDisabled(!can_save);
1926 ValidateCurrentFile();
1928 ImGui::EndDisabled();
1929 if (ImGui::IsItemHovered())
1930 ImGui::SetTooltip(
"Validate (Ctrl+B)");
1933 bool can_apply = can_save && rom_ && rom_->is_loaded();
1934 ImGui::BeginDisabled(!can_apply);
1935 if (ImGui::Button(
ICON_MD_BUILD, ImVec2(button_size, button_size))) {
1938 ImGui::EndDisabled();
1939 if (ImGui::IsItemHovered())
1940 ImGui::SetTooltip(
"Apply to ROM (Ctrl+Shift+B)");
1943void AssemblyEditor::DrawFileTabView() {
1944 if (active_files_.empty()) {
1949 ImGuiTabBarFlags_Reorderable |
1950 ImGuiTabBarFlags_AutoSelectNewTabs |
1951 ImGuiTabBarFlags_FittingPolicyScroll)) {
1952 for (
int i = 0; i < active_files_.Size; i++) {
1953 int file_id = active_files_[i];
1954 if (file_id >= files_.size()) {
1959 std::string filename = files_[file_id];
1960 size_t pos = filename.find_last_of(
"/\\");
1961 if (pos != std::string::npos) {
1962 filename = filename.substr(pos + 1);
1965 bool is_active = (active_file_id_ == file_id);
1966 ImGuiTabItemFlags flags = is_active ? ImGuiTabItemFlags_SetSelected : 0;
1967 bool tab_open =
true;
1969 if (ImGui::BeginTabItem(filename.c_str(), &tab_open, flags)) {
1972 active_file_id_ = file_id;
1975 ImGui::EndTabItem();
1980 active_files_.erase(active_files_.Data + i);
1981 if (active_file_id_ == file_id) {
1982 active_file_id_ = active_files_.empty() ? -1 : active_files_[0];
1983 if (active_file_id_ >= 0 &&
1984 active_file_id_ <
static_cast<int>(open_files_.size())) {
1987 current_file_.clear();
2001absl::Status AssemblyEditor::Update() {
2003 return absl::OkStatus();
2006 if (dependencies_.window_manager !=
nullptr) {
2007 return absl::OkStatus();
2012 ImGui::Begin(
"Assembly Editor", &active_, ImGuiWindowFlags_MenuBar);
2019 return absl::OkStatus();
2022void AssemblyEditor::InlineUpdate() {
2025 const char* file_label =
2026 current_file_.empty() ?
"No file" : current_file_.c_str();
2027 ImGui::Text(
"%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
2030 editor->
CanUndo() ?
"*" :
" ",
2033 editor->
Render(
"##asm_editor", ImVec2(0, 0));
2036void AssemblyEditor::UpdateCodeView() {
2039 DrawToolbarContent();
2044absl::Status AssemblyEditor::Save() {
2045 if (!HasActiveFile()) {
2046 return absl::FailedPreconditionError(
"No active file to save.");
2049 const std::string& path = files_[active_file_id_];
2050 std::ofstream file(path);
2051 if (!file.is_open()) {
2052 return absl::InvalidArgumentError(
2053 absl::StrCat(
"Cannot write file: ", path));
2056 file << GetActiveEditor()->GetText();
2058 return absl::OkStatus();
2061void AssemblyEditor::DrawToolset() {
2066 current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
2075void AssemblyEditor::DrawCurrentFolder() {
2077 if (current_folder_.name.empty() && dependencies_.project &&
2078 !dependencies_.project->code_folder.empty()) {
2079 OpenFolder(dependencies_.project->GetAbsolutePath(
2080 dependencies_.project->code_folder));
2083 if (ImGui::BeginChild(
"##current_folder", ImVec2(0, 0),
true,
2084 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
2085 if (ImGui::BeginTable(
"##file_table", 2,
2086 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
2087 ImGuiTableFlags_Resizable |
2088 ImGuiTableFlags_Sortable)) {
2089 ImGui::TableSetupColumn(
"Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
2090 ImGui::TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthStretch);
2092 ImGui::TableHeadersRow();
2094 for (
const auto& file : current_folder_.files) {
2095 ImGui::TableNextRow();
2096 ImGui::TableNextColumn();
2097 if (ImGui::Selectable(file.c_str())) {
2098 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/", file));
2100 ImGui::TableNextColumn();
2101 ImGui::Text(
"File");
2104 for (
const auto& subfolder : current_folder_.subfolders) {
2105 ImGui::TableNextRow();
2106 ImGui::TableNextColumn();
2107 if (ImGui::TreeNode(subfolder.name.c_str())) {
2108 for (
const auto& file : subfolder.files) {
2109 ImGui::TableNextRow();
2110 ImGui::TableNextColumn();
2111 if (ImGui::Selectable(file.c_str())) {
2112 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/",
2113 subfolder.name,
"/", file));
2115 ImGui::TableNextColumn();
2116 ImGui::Text(
"File");
2120 ImGui::TableNextColumn();
2121 ImGui::Text(
"Folder");
2131void AssemblyEditor::DrawFileMenu() {
2132 if (ImGui::BeginMenu(
"File")) {
2135 if (!filename.empty()) {
2136 ChangeActiveFile(filename);
2146void AssemblyEditor::DrawEditMenu() {
2147 if (ImGui::BeginMenu(
"Edit")) {
2149 GetActiveEditor()->Undo();
2152 GetActiveEditor()->Redo();
2156 GetActiveEditor()->Cut();
2159 GetActiveEditor()->Copy();
2162 GetActiveEditor()->Paste();
2172void AssemblyEditor::ChangeActiveFile(
const std::string_view& filename) {
2173 if (filename.empty()) {
2178 for (
int i = 0; i < active_files_.Size; ++i) {
2179 int file_id = active_files_[i];
2180 if (files_[file_id] == filename) {
2182 active_file_id_ = file_id;
2191 int new_file_id = files_.size();
2192 files_.push_back(std::string(filename));
2193 active_files_.push_back(new_file_id);
2196 if (new_file_id >= open_files_.size()) {
2197 open_files_.resize(new_file_id + 1);
2200 open_files_[new_file_id].SetText(content);
2201 open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
2203 open_files_[new_file_id].SetShowWhitespaces(
false);
2204 active_file_id_ = new_file_id;
2206 }
catch (
const std::exception& ex) {
2207 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error opening file: %s\n",
2212absl::Status AssemblyEditor::Cut() {
2213 GetActiveEditor()->Cut();
2214 return absl::OkStatus();
2217absl::Status AssemblyEditor::Copy() {
2218 GetActiveEditor()->Copy();
2219 return absl::OkStatus();
2222absl::Status AssemblyEditor::Paste() {
2223 GetActiveEditor()->Paste();
2224 return absl::OkStatus();
2227absl::Status AssemblyEditor::Undo() {
2228 GetActiveEditor()->Undo();
2229 return absl::OkStatus();
2232absl::Status AssemblyEditor::Redo() {
2233 GetActiveEditor()->Redo();
2234 return absl::OkStatus();
2237#ifdef YAZE_WITH_Z3DK
2240 if (!dependencies_.project) {
2244 const auto&
z3dk = dependencies_.project->z3dk_settings;
2259 for (
const auto& range :
z3dk.prohibited_memory_ranges) {
2261 {.start = range.start, .end = range.end, .reason = range.reason});
2264 auto append_unique = [&options](
const std::string& path) {
2274 append_unique(dependencies_.project->code_folder);
2275 if (HasActiveFile()) {
2277 std::filesystem::path(files_[active_file_id_]).parent_path().
string());
2280 if (rom_ && rom_->is_loaded() && !rom_->filename().empty()) {
2282 }
else if (!
z3dk.rom_path.empty()) {
2289void AssemblyEditor::ExportZ3dkArtifacts(
const core::AsarPatchResult& result,
2290 bool sync_mesen_symbols) {
2291 if (!dependencies_.project) {
2295 const auto& project = *dependencies_.project;
2296 const auto& configured = project.z3dk_settings.artifact_paths;
2297 const std::string symbols_path =
2298 configured.symbols_mlb.empty()
2299 ? project.GetZ3dkArtifactPath(
"symbols.mlb")
2300 : configured.symbols_mlb;
2301 const std::string sourcemap_path =
2302 configured.sourcemap_json.empty()
2303 ? project.GetZ3dkArtifactPath(
"sourcemap.json")
2304 : configured.sourcemap_json;
2305 const std::string annotations_path =
2306 configured.annotations_json.empty()
2307 ? project.GetZ3dkArtifactPath(
"annotations.json")
2308 : configured.annotations_json;
2309 const std::string hooks_path = configured.hooks_json.empty()
2310 ? project.GetZ3dkArtifactPath(
"hooks.json")
2311 : configured.hooks_json;
2312 const std::string lint_path = configured.lint_json.empty()
2313 ? project.GetZ3dkArtifactPath(
"lint.json")
2314 : configured.lint_json;
2315 auto write_artifact = [](
const std::string& path,
2316 const std::string& content) {
2317 if (path.empty() || content.empty()) {
2321 std::filesystem::create_directories(
2322 std::filesystem::path(path).parent_path(), ec);
2323 std::ofstream file(path, std::ios::binary | std::ios::trunc);
2324 if (!file.is_open()) {
2330 write_artifact(symbols_path, result.symbols_mlb);
2331 write_artifact(sourcemap_path, result.sourcemap_json);
2332 write_artifact(annotations_path, result.annotations_json);
2333 write_artifact(hooks_path, result.hooks_json);
2334 write_artifact(lint_path, result.lint_json);
2336 if (!sync_mesen_symbols || symbols_path.empty() ||
2337 result.symbols_mlb.empty()) {
2342 if (!client->IsConnected()) {
2343 client->Connect().IgnoreError();
2345 if (!client->IsConnected()) {
2349 auto status = client->LoadSymbolsFile(symbols_path);
2351 if (dependencies_.toast_manager) {
2352 dependencies_.toast_manager->Show(
2353 "Failed to sync Mesen2 symbols: " + std::string(status.message()),
2354 ToastType::kWarning);
2356 }
else if (dependencies_.toast_manager) {
2357 dependencies_.toast_manager->Show(
2358 "Synced symbols to Mesen2 from " + symbols_path, ToastType::kSuccess);
2367absl::Status AssemblyEditor::ValidateCurrentFile() {
2368 if (!HasActiveFile()) {
2369 return absl::FailedPreconditionError(
"No file is currently active");
2372 const std::string& file_path = files_[active_file_id_];
2374#ifdef YAZE_WITH_Z3DK
2375 const auto options = BuildZ3dkAssembleOptions();
2379 std::vector<uint8_t> scratch;
2380 auto result_or = z3dk_.ApplyPatch(file_path, scratch, options);
2381 if (!result_or.ok()) {
2382 last_errors_.clear();
2383 last_errors_.push_back(std::string(result_or.status().message()));
2384 last_warnings_.clear();
2385 last_diagnostics_.clear();
2387 GetActiveEditor()->SetErrorMarkers(empty_markers);
2388 return result_or.status();
2390 UpdateErrorMarkers(*result_or);
2391 if (!result_or->success) {
2392 return absl::InternalError(
"Assembly validation failed");
2394 ExportZ3dkArtifacts(*result_or,
false);
2395 ClearErrorMarkers();
2396 return absl::OkStatus();
2399 if (!asar_initialized_) {
2400 auto status = asar_.Initialize();
2404 asar_initialized_ =
true;
2408 auto status = asar_.ValidateAssembly(file_path);
2413 last_errors_.clear();
2414 last_errors_.push_back(std::string(status.message()));
2418 for (
const auto& error : last_errors_) {
2420 size_t line_pos = error.find(
':');
2421 if (line_pos != std::string::npos) {
2422 size_t num_start = line_pos + 1;
2423 size_t num_end = error.find(
':', num_start);
2424 if (num_end != std::string::npos) {
2425 std::string line_str = error.substr(num_start, num_end - num_start);
2427 int line = std::stoi(line_str);
2428 markers[line] = error;
2435 GetActiveEditor()->SetErrorMarkers(markers);
2440 ClearErrorMarkers();
2441 return absl::OkStatus();
2445absl::Status AssemblyEditor::ApplyPatchToRom() {
2446 if (!rom_ || !rom_->is_loaded()) {
2447 return absl::FailedPreconditionError(
"No ROM is loaded");
2450 if (!HasActiveFile()) {
2451 return absl::FailedPreconditionError(
"No file is currently active");
2454 const std::string& file_path = files_[active_file_id_];
2455 std::vector<uint8_t> rom_data = rom_->vector();
2457#ifdef YAZE_WITH_Z3DK
2458 const auto options = BuildZ3dkAssembleOptions();
2459 auto result = z3dk_.ApplyPatch(file_path, rom_data, options);
2461 last_errors_.clear();
2462 last_errors_.push_back(std::string(result.status().message()));
2463 last_warnings_.clear();
2464 last_diagnostics_.clear();
2466 GetActiveEditor()->SetErrorMarkers(empty_markers);
2467 return result.status();
2469 UpdateErrorMarkers(*result);
2470 if (!result->success) {
2471 return absl::InternalError(
"Patch application failed");
2473 rom_->LoadFromData(rom_data);
2474 symbols_ = z3dk_.GetSymbolTable();
2475 ExportZ3dkArtifacts(*result,
true);
2476 ClearErrorMarkers();
2477 return absl::OkStatus();
2480 if (!asar_initialized_) {
2481 auto status = asar_.Initialize();
2485 asar_initialized_ =
true;
2489 auto result = asar_.ApplyPatch(file_path, rom_data);
2492 UpdateErrorMarkers(*result);
2493 return result.status();
2496 if (result->success) {
2498 rom_->LoadFromData(rom_data);
2501 symbols_ = asar_.GetSymbolTable();
2502 last_errors_.clear();
2503 last_warnings_ = result->warnings;
2506 ClearErrorMarkers();
2508 return absl::OkStatus();
2510 UpdateErrorMarkers(*result);
2511 return absl::InternalError(
"Patch application failed");
2517 last_errors_ = result.
errors;
2521 if (!HasActiveFile()) {
2535 markers[d.line] = d.message;
2540 for (
const auto& error : result.
errors) {
2542 size_t first_colon = error.find(
':');
2543 if (first_colon != std::string::npos) {
2544 size_t second_colon = error.find(
':', first_colon + 1);
2545 if (second_colon != std::string::npos) {
2546 std::string line_str =
2547 error.substr(first_colon + 1, second_colon - (first_colon + 1));
2548 int line = std::stoi(line_str);
2549 markers[line] = error;
2558 GetActiveEditor()->SetErrorMarkers(markers);
2561void AssemblyEditor::ClearErrorMarkers() {
2562 last_errors_.clear();
2563 last_diagnostics_.clear();
2565 if (!HasActiveFile()) {
2570 GetActiveEditor()->SetErrorMarkers(empty_markers);
2573void AssemblyEditor::DrawAssembleMenu() {
2574 if (ImGui::BeginMenu(
"Assemble")) {
2575 bool has_active_file = HasActiveFile();
2576 bool has_rom = (rom_ && rom_->is_loaded());
2580 auto status = ValidateCurrentFile();
2586 if (ImGui::MenuItem(
ICON_MD_BUILD " Apply to ROM",
"Ctrl+Shift+B",
false,
2587 has_active_file && has_rom)) {
2588 auto status = ApplyPatchToRom();
2596 if (dependencies_.project) {
2597 std::string sym_file = dependencies_.project->symbols_filename;
2598 if (dependencies_.project->HasZ3dkConfig() &&
2599 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
2602 dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb;
2603 }
else if (sym_file.empty() || !std::filesystem::exists(sym_file)) {
2604 sym_file = dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
2606 if (!sym_file.empty()) {
2607 std::string abs_path = sym_file;
2608 if (!std::filesystem::path(abs_path).is_absolute()) {
2609 abs_path = dependencies_.project->GetAbsolutePath(sym_file);
2611 auto status = asar_.LoadSymbolsFromFile(abs_path);
2614 symbols_ = asar_.GetSymbolTable();
2615 if (dependencies_.toast_manager) {
2616 dependencies_.toast_manager->Show(
2617 "Successfully loaded external symbols from " + sym_file,
2618 ToastType::kSuccess);
2621 if (dependencies_.toast_manager) {
2622 dependencies_.toast_manager->Show(
2623 "Failed to load symbols: " + std::string(status.message()),
2628 if (dependencies_.toast_manager) {
2629 dependencies_.toast_manager->Show(
2630 "Project does not specify a symbols file.",
2631 ToastType::kWarning);
2639 if (ImGui::MenuItem(
ICON_MD_LIST " Show Symbols",
nullptr,
2640 show_symbol_panel_)) {
2641 show_symbol_panel_ = !show_symbol_panel_;
2647 ImGui::TextDisabled(
"Errors: %zu, Warnings: %zu", last_errors_.size(),
2648 last_warnings_.size());
2653 if (ImGui::BeginMenu(
"Version")) {
2654 bool has_version_manager = (dependencies_.version_manager !=
nullptr);
2656 has_version_manager)) {
2657 if (has_version_manager) {
2658 ImGui::OpenPopup(
"Create Snapshot");
2663 if (ImGui::BeginPopupModal(
"Create Snapshot",
nullptr,
2664 ImGuiWindowFlags_AlwaysAutoResize)) {
2665 static char message[256] =
"";
2666 ImGui::InputText(
"Message", message,
sizeof(message));
2668 if (ImGui::Button(
"Create", ImVec2(120, 0))) {
2669 auto result = dependencies_.version_manager->CreateSnapshot(message);
2670 if (result.ok() && result->success) {
2671 if (dependencies_.toast_manager) {
2672 dependencies_.toast_manager->Show(
2673 "Snapshot Created: " + result->commit_hash,
2674 ToastType::kSuccess);
2677 if (dependencies_.toast_manager) {
2678 std::string err = result.ok()
2680 : std::string(result.status().message());
2681 dependencies_.toast_manager->Show(
"Snapshot Failed: " + err,
2685 ImGui::CloseCurrentPopup();
2689 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
2690 ImGui::CloseCurrentPopup();
2699void AssemblyEditor::DrawSymbolPanel() {
2700 if (!show_symbol_panel_) {
2704 ImGui::SetNextWindowSize(ImVec2(350, 400), ImGuiCond_FirstUseEver);
2705 if (ImGui::Begin(
"Symbols", &show_symbol_panel_)) {
2706 if (symbols_.empty()) {
2707 ImGui::TextDisabled(
"No symbols loaded.");
2708 ImGui::TextDisabled(
"Apply a patch to load symbols.");
2711 static char filter[256] =
"";
2712 ImGui::InputTextWithHint(
"##symbol_filter",
"Filter symbols...", filter,
2717 if (ImGui::BeginChild(
"##symbol_list", ImVec2(0, 0),
true)) {
2718 for (
const auto& [name, symbol] : symbols_) {
2720 if (filter[0] !=
'\0' && name.find(filter) == std::string::npos) {
2724 ImGui::PushID(name.c_str());
2725 if (ImGui::Selectable(name.c_str())) {
2729 ImGui::SameLine(200);
2730 ImGui::TextDisabled(
"$%06X", symbol.address);
static const Palette & GetDarkPalette()
Coordinates GetCursorPosition() const
int GetTotalLines() const
void Render(const char *aTitle, const ImVec2 &aSize=ImVec2(), bool aBorder=false)
std::map< int, std::string > ErrorMarkers
const LanguageDefinition & GetLanguageDefinition() const
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
bool contains(const std::string &) const
absl::Status Run(const std::vector< std::string > &args, Rom *rom_context, std::string *captured_output=nullptr)
Execute the command.
virtual void SetAsarWrapper(core::AsarWrapper *asar_wrapper)
Set the AsarWrapper context. Default implementation does nothing, override if tool needs Asar access.
virtual void SetProjectContext(project::YazeProject *project)
Set the YazeProject context. Default implementation does nothing, override if tool needs project info...
virtual void SetAssemblySymbolTable(const std::map< std::string, core::AsarSymbol > *table)
Optional Asar symbol table for assembly-aware tools.
std::string disasm_query_
std::string current_file_
TextEditor::Coordinates active_cursor_position() const
absl::Status ValidateCurrentFile()
std::vector< std::string > last_warnings_
std::map< std::string, core::AsarSymbol > symbols_
void DrawDisassemblyContent()
std::optional< uint32_t > CurrentDisassemblyBank() const
std::vector< TextEditor > open_files_
bool HasActiveFile() const
absl::Status Load() override
void Initialize() override
std::string ResolveZ3DisasmOutputDir() const
std::string BuildProjectGraphLookupQuery(uint32_t address) const
std::vector< Z3DisasmHookJump > z3disasm_hook_jumps_
void ClearSymbolJumpCache()
std::string z3disasm_output_dir_
void ChangeActiveFile(const std::string_view &filename)
absl::Status JumpToReference(const std::string &reference)
TextEditor * GetActiveEditor()
absl::flat_hash_map< std::string, AsmSymbolLocation > symbol_jump_cache_
std::string ResolveZ3DisasmRomPath() const
std::vector< core::AssemblyDiagnostic > last_diagnostics_
void OpenFolder(const std::string &folder_path)
absl::flat_hash_set< std::string > symbol_jump_negative_cache_
std::string active_file_path() const
std::string z3disasm_selected_path_
std::string BuildProjectGraphBankQuery() const
void RefreshSelectedZ3DisassemblyMetadata()
FolderItem current_folder_
std::string ResolveZ3DisasmCommand() const
std::vector< std::string > files_
void DrawToolbarContent()
int SelectedZ3DisasmBankIndex() const
absl::Status NavigateDisassemblyQuery()
absl::Status RunProjectGraphQueryInDrawer(const std::vector< std::string > &args, const std::string &title)
void DrawSymbolsContent()
absl::Status ApplyPatchToRom()
std::vector< std::string > last_errors_
std::string symbol_jump_root_
std::vector< Z3DisasmSourceJump > z3disasm_source_jumps_
absl::Status JumpToSymbolDefinition(const std::string &symbol)
The EditorManager controls the main editor window and manages the various editor classes.
EditorDependencies dependencies_
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
void RegisterWindowContent(std::unique_ptr< WindowContent > window)
Register a WindowContent instance for central drawing.
65816 CPU disassembler for debugging and ROM hacking
std::vector< DisassembledInstruction > DisassembleRange(uint32_t start_address, size_t count, MemoryReader read_byte, bool m_flag=true, bool x_flag=true) const
Disassemble multiple instructions.
void SetSymbolResolver(SymbolResolver resolver)
Set optional symbol resolver for address lookups.
static std::shared_ptr< MesenSocketClient > GetOrCreate()
RAII guard for ImGui style colors.
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_CONTENT_CUT
#define ICON_MD_FILE_OPEN
#define ICON_MD_CAMERA_ALT
#define ICON_MD_FILE_UPLOAD
#define ICON_MD_CONTENT_PASTE
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_CONTENT_COPY
std::optional< int > ParsePositiveInt(const std::string &s)
bool LooksLikeAssemblyPathRef(const std::string &file_ref)
int NormalizeLoRomBankIndex(uint32_t address)
bool LoadJsonFile(const std::string &path, Json *out)
std::optional< std::filesystem::path > FindUpwardPath(const std::filesystem::path &start, const std::filesystem::path &relative)
std::optional< AsmFileSymbolRef > ParseAsmFileSymbolRef(const std::string &reference)
std::optional< AsmSymbolLocation > FindLabelInFolder(const std::filesystem::path &root, const std::string &label)
std::optional< uint32_t > ParseHexAddress(const std::string &text)
std::optional< AsmSymbolLocation > FindLabelInFile(const std::filesystem::path &path, const std::string &label)
bool ShouldSkipDirectory(const std::string &name)
std::optional< AsmFileLineRef > ParseAsmFileLineRef(const std::string &reference)
bool IsIgnoredFile(const std::string &name, const std::vector< std::string > &ignored_files)
FolderItem LoadFolder(const std::string &folder)
std::optional< int > ParseGeneratedBankIndex(const std::string &path)
std::optional< std::filesystem::path > FindAsmFileInFolder(const std::filesystem::path &root, const std::string &file_ref)
void SortFolderItem(FolderItem *item)
bool HasFileExtension(const std::string &name)
bool IsAssemblyLikeFile(const std::filesystem::path &path)
bool IsGeneratedBankFile(const std::filesystem::path &path)
std::string ShellQuote(const std::string &value)
bool IsHiddenName(const std::string &name)
Editors are the view controllers for the application.
void DrawDiagnosticsPanel(std::span< const core::AssemblyDiagnostic > diagnostics, const DiagnosticsPanelCallbacks &callbacks)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
TextEditor::LanguageDefinition GetAssemblyLanguageDef()
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
std::string LoadFile(const std::string &filename)
Loads the entire contents of a file into a string.
uint32_t SnesToPc(uint32_t addr) noexcept
static Coordinates Invalid()
TokenRegexStrings mTokenRegexStrings
std::string mSingleLineComment
std::string mCommentStart
Asar patch result information.
std::vector< AssemblyDiagnostic > structured_diagnostics
std::vector< std::string > errors
std::vector< std::string > warnings
bool warn_branch_outside_bank
std::vector< Z3dkMemoryRange > prohibited_memory_ranges
bool capture_nocash_symbols
std::vector< std::string > include_paths
std::vector< std::pair< std::string, std::string > > defines
std::string std_defines_path
bool warn_unauthorized_hook
std::string std_includes_path
std::string hooks_rom_path
std::function< void(const std::string &file, int line, int column) on_diagnostic_activated)
project::YazeProject * project
ToastManager * toast_manager
WorkspaceWindowManager * window_manager
std::vector< FolderItem > subfolders
std::vector< std::string > files
std::string GetZ3dkArtifactPath(absl::string_view artifact_name) const
std::string GetAbsolutePath(const std::string &relative_path) const
Z3dkSettings z3dk_settings