14#include <TargetConditionals.h>
19#include "absl/strings/ascii.h"
20#include "absl/strings/match.h"
21#include "absl/strings/str_cat.h"
22#include "absl/strings/str_format.h"
23#include "absl/strings/str_join.h"
39#include "imgui/misc/cpp/imgui_stdlib.h"
46using util::FileDialogWrapper;
50static const char*
const kKeywords[] = {
51 "ADC",
"AND",
"ASL",
"BCC",
"BCS",
"BEQ",
"BIT",
"BMI",
"BNE",
"BPL",
52 "BRA",
"BRL",
"BVC",
"BVS",
"CLC",
"CLD",
"CLI",
"CLV",
"CMP",
"CPX",
53 "CPY",
"DEC",
"DEX",
"DEY",
"EOR",
"INC",
"INX",
"INY",
"JMP",
"JSR",
54 "JSL",
"LDA",
"LDX",
"LDY",
"LSR",
"MVN",
"NOP",
"ORA",
"PEA",
"PER",
55 "PHA",
"PHB",
"PHD",
"PHP",
"PHX",
"PHY",
"PLA",
"PLB",
"PLD",
"PLP",
56 "PLX",
"PLY",
"REP",
"ROL",
"ROR",
"RTI",
"RTL",
"RTS",
"SBC",
"SEC",
57 "SEI",
"SEP",
"STA",
"STP",
"STX",
"STY",
"STZ",
"TAX",
"TAY",
"TCD",
58 "TCS",
"TDC",
"TRB",
"TSB",
"TSC",
"TSX",
"TXA",
"TXS",
"TXY",
"TYA",
59 "TYX",
"WAI",
"WDM",
"XBA",
"XCE",
"ORG",
"LOROM",
"HIROM"};
61static const char*
const kIdentifiers[] = {
62 "abort",
"abs",
"acos",
"asin",
"atan",
"atexit",
63 "atof",
"atoi",
"atol",
"ceil",
"clock",
"cosh",
64 "ctime",
"div",
"exit",
"fabs",
"floor",
"fmod",
65 "getchar",
"getenv",
"isalnum",
"isalpha",
"isdigit",
"isgraph",
66 "ispunct",
"isspace",
"isupper",
"kbhit",
"log10",
"log2",
67 "log",
"memcmp",
"modf",
"pow",
"putchar",
"putenv",
68 "puts",
"rand",
"remove",
"rename",
"sinh",
"sqrt",
69 "srand",
"strcat",
"strcmp",
"strerror",
"time",
"tolower",
74 for (
auto& k : kKeywords)
77 for (
auto& k : kIdentifiers) {
80 language_65816.
mIdentifiers.insert(std::make_pair(std::string(k),
id));
84 std::make_pair<std::string, TextEditor::PaletteIndex>(
87 std::make_pair<std::string, TextEditor::PaletteIndex>(
90 std::make_pair<std::string, TextEditor::PaletteIndex>(
93 std::make_pair<std::string, TextEditor::PaletteIndex>(
94 "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
97 std::make_pair<std::string, TextEditor::PaletteIndex>(
100 std::make_pair<std::string, TextEditor::PaletteIndex>(
103 std::make_pair<std::string, TextEditor::PaletteIndex>(
104 "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
107 std::make_pair<std::string, TextEditor::PaletteIndex>(
110 std::make_pair<std::string, TextEditor::PaletteIndex>(
111 "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
122 language_65816.
mName =
"65816";
124 return language_65816;
128 return absl::StrContains(name,
'.');
132 return !name.empty() && name[0] ==
'.';
136 const std::vector<std::string>& ignored_files) {
137 return std::ranges::find(ignored_files, name) != ignored_files.end();
141 static const std::array<const char*, 13> kSkippedDirectories = {
142 ".git",
".context",
".idea",
".vscode",
"build",
143 "build_ai",
"build_agent",
"build_test",
"build-ios",
"build-ios-sim",
144 "build-wasm",
"node_modules",
"dist"};
145 for (
const char* skipped : kSkippedDirectories) {
146 if (name == skipped) {
157 std::string quoted =
"'";
158 for (
char ch : value) {
162 quoted.push_back(ch);
170 const std::filesystem::path& start,
const std::filesystem::path& relative) {
172 auto current = std::filesystem::absolute(start, ec);
176 while (!current.empty()) {
177 const auto candidate = current / relative;
178 if (std::filesystem::exists(candidate, ec) && !ec) {
181 if (current == current.root_path() || current == current.parent_path()) {
184 current = current.parent_path();
190 if (path.extension() !=
".asm") {
193 const std::string name = path.filename().string();
194 return absl::StartsWith(name,
"bank_");
201 std::ifstream file(path);
202 if (!file.is_open()) {
214 if ((address & 0xFF0000u) >= 0x800000u) {
215 address &= 0x7FFFFFu;
217 return static_cast<int>((address >> 16) & 0xFFu);
221 const std::string name = std::filesystem::path(path).filename().string();
222 if (!absl::StartsWith(name,
"bank_") || !absl::EndsWith(name,
".asm")) {
225 const std::string hex = name.substr(5, name.size() - 9);
227 return std::stoi(hex,
nullptr, 16);
238 std::string token = text;
239 const size_t first = token.find_first_not_of(
" \t");
240 if (first == std::string::npos) {
243 const size_t last = token.find_last_not_of(
" \t");
244 token = token.substr(first, last - first + 1);
248 if (token[0] ==
'$') {
249 token = token.substr(1);
250 }
else if (token.size() > 2 && token[0] ==
'0' &&
251 (token[1] ==
'x' || token[1] ==
'X')) {
252 token = token.substr(2);
255 return static_cast<uint32_t
>(std::stoul(token,
nullptr, 16));
265 std::sort(item->
files.begin(), item->
files.end());
268 return lhs.name < rhs.name;
276 std::vector<std::string> ignored_files;
277#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
278 std::ifstream gitignore(folder +
"/.gitignore");
279 if (gitignore.good()) {
281 while (std::getline(gitignore, line)) {
282 if (line.empty() || line[0] ==
'#' || line[0] ==
'!') {
285 ignored_files.push_back(line);
292 std::error_code path_ec;
293 std::filesystem::path root_path =
294 std::filesystem::weakly_canonical(folder, path_ec);
297 root_path = std::filesystem::absolute(folder, path_ec);
302 current_folder.
name = root_path.string();
304 std::error_code root_ec;
305 for (
const auto& entry :
306 std::filesystem::directory_iterator(root_path, root_ec)) {
311 const std::string entry_name = entry.path().filename().string();
316 std::error_code type_ec;
317 if (entry.is_regular_file(type_ec)) {
322 current_folder.
files.push_back(entry_name);
331 folder_item.
name = entry_name;
333 std::error_code sub_ec;
334 for (
const auto& sub_entry :
335 std::filesystem::directory_iterator(entry.path(), sub_ec)) {
340 const std::string sub_name = sub_entry.path().filename().string();
345 std::error_code sub_type_ec;
346 if (sub_entry.is_regular_file(sub_type_ec)) {
351 folder_item.
files.push_back(sub_name);
355 if (!sub_entry.is_directory(sub_type_ec) ||
361 subfolder_item.
name = sub_name;
362 std::error_code leaf_ec;
363 for (
const auto& leaf_entry :
364 std::filesystem::directory_iterator(sub_entry.path(), leaf_ec)) {
368 const std::string leaf_name = leaf_entry.path().filename().string();
372 std::error_code leaf_type_ec;
373 if (!leaf_entry.is_regular_file(leaf_type_ec)) {
380 subfolder_item.
files.push_back(leaf_name);
382 folder_item.
subfolders.push_back(std::move(subfolder_item));
385 current_folder.
subfolders.push_back(std::move(folder_item));
389 return current_folder;
393 const std::filesystem::path& path,
const std::string& label) {
394 std::ifstream file(path);
395 if (!file.is_open()) {
401 while (std::getline(file, line)) {
402 const size_t start = line.find_first_not_of(
" \t");
403 if (start == std::string::npos) {
408 if (line.compare(start, label.size(), label) != 0) {
413 size_t pos = start + label.size();
414 while (pos < line.size() && (line[pos] ==
' ' || line[pos] ==
'\t')) {
418 if (pos < line.size() && line[pos] ==
':') {
420 loc.
file = path.string();
421 loc.
line = line_index;
422 loc.
column =
static_cast<int>(start);
433 const auto ext = path.extension().string();
434 return ext ==
".asm" || ext ==
".inc" || ext ==
".s";
439 int line_one_based = 0;
440 int column_one_based = 1;
453 if (!std::isdigit(
static_cast<unsigned char>(c))) {
458 const int v = std::stoi(s);
459 return v > 0 ? std::optional<int>(v) : std::nullopt;
466 if (file_ref.empty()) {
469 const std::filesystem::path p(file_ref);
474 const std::string& reference) {
475 const std::string trimmed =
476 std::string(absl::StripAsciiWhitespace(reference));
477 if (trimmed.empty()) {
482 if (
const size_t pos = trimmed.find(
"#L"); pos != std::string::npos) {
483 const std::string file =
484 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, pos)));
485 const std::string line_str =
486 std::string(absl::StripAsciiWhitespace(trimmed.substr(pos + 2)));
499 const size_t last_colon = trimmed.rfind(
':');
500 if (last_colon == std::string::npos) {
504 const std::string tail =
505 std::string(absl::StripAsciiWhitespace(trimmed.substr(last_colon + 1)));
510 const size_t second_last_colon = (last_colon == 0)
512 : trimmed.rfind(
':', last_colon - 1);
514 if (second_last_colon != std::string::npos) {
515 const std::string file = std::string(
516 absl::StripAsciiWhitespace(trimmed.substr(0, second_last_colon)));
517 const std::string line_str =
518 std::string(absl::StripAsciiWhitespace(trimmed.substr(
519 second_last_colon + 1, last_colon - second_last_colon - 1)));
520 const std::string col_str = tail;
526 if (!line.has_value() || !col.has_value()) {
532 const std::string file =
533 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, last_colon)));
544 const std::string& reference) {
545 const std::string trimmed =
546 std::string(absl::StripAsciiWhitespace(reference));
547 if (trimmed.empty()) {
552 if (
const size_t pos = trimmed.rfind(
'#'); pos != std::string::npos) {
553 const std::string file =
554 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, pos)));
555 const std::string sym =
556 std::string(absl::StripAsciiWhitespace(trimmed.substr(pos + 1)));
564 const size_t last_colon = trimmed.rfind(
':');
565 if (last_colon == std::string::npos) {
569 const std::string file =
570 std::string(absl::StripAsciiWhitespace(trimmed.substr(0, last_colon)));
571 const std::string sym =
572 std::string(absl::StripAsciiWhitespace(trimmed.substr(last_colon + 1)));
587 const std::filesystem::path& root,
const std::string& file_ref) {
588 std::filesystem::path p(file_ref);
589 if (p.is_absolute()) {
591 if (std::filesystem::exists(p, ec) &&
592 std::filesystem::is_regular_file(p, ec)) {
600 const std::filesystem::path candidate = root / p;
602 if (std::filesystem::exists(candidate, ec) &&
603 std::filesystem::is_regular_file(candidate, ec)) {
609 const std::string want_suffix = p.generic_string();
610 const std::string want_name = p.filename().string();
613 if (!std::filesystem::exists(root, ec)) {
617 std::filesystem::recursive_directory_iterator it(
618 root, std::filesystem::directory_options::skip_permission_denied, ec);
619 const std::filesystem::recursive_directory_iterator end;
620 for (; it != end && !ec; it.increment(ec)) {
621 const auto& entry = *it;
622 if (entry.is_directory()) {
623 const auto name = entry.path().filename().string();
624 if (!name.empty() && name.front() ==
'.') {
625 it.disable_recursion_pending();
627 it.disable_recursion_pending();
632 if (!entry.is_regular_file()) {
639 const std::string cand = entry.path().generic_string();
640 if (!want_suffix.empty() && absl::EndsWith(cand, want_suffix)) {
643 if (!want_name.empty() && entry.path().filename() == want_name) {
652 const std::filesystem::path& root,
const std::string& label) {
654 if (!std::filesystem::exists(root, ec)) {
658 std::filesystem::recursive_directory_iterator it(
659 root, std::filesystem::directory_options::skip_permission_denied, ec);
660 const std::filesystem::recursive_directory_iterator end;
661 for (; it != end && !ec; it.increment(ec)) {
662 const auto& entry = *it;
663 if (entry.is_directory()) {
664 const auto name = entry.path().filename().string();
665 if (!name.empty() && name.front() ==
'.') {
666 it.disable_recursion_pending();
668 it.disable_recursion_pending();
673 if (!entry.is_regular_file()) {
681 if (
auto loc =
FindLabelInFile(entry.path(), label); loc.has_value()) {
701 std::make_unique<AssemblyCodeEditorPanel>(
705 window_manager->RegisterWindowContent(
706 std::make_unique<AssemblyFileBrowserPanel>(
710 window_manager->RegisterWindowContent(std::make_unique<AssemblySymbolsPanel>(
714 window_manager->RegisterWindowContent(
715 std::make_unique<AssemblyBuildOutputPanel>(
718 window_manager->RegisterWindowContent(
719 std::make_unique<AssemblyDisassemblyPanel>(
723 window_manager->RegisterWindowContent(std::make_unique<AssemblyToolbarPanel>(
729 return absl::OkStatus();
733 if (symbol.empty()) {
734 return absl::InvalidArgumentError(
"Symbol is empty");
737 std::filesystem::path root;
744 return absl::FailedPreconditionError(
745 "No code folder loaded (open a folder or set project code_folder)");
748 const std::string root_string = root.string();
761 const auto& cached = it->second;
764 return absl::InternalError(
"Failed to open file for symbol: " + symbol);
769 return absl::InternalError(
"No active text editor");
772 editor->SetCursorPosition(
774 editor->SelectWordUnderCursor();
775 return absl::OkStatus();
779 return absl::NotFoundError(
"Symbol not found: " + symbol);
782 const auto loc = FindLabelInFolder(root, symbol);
783 if (!loc.has_value()) {
785 return absl::NotFoundError(
"Symbol not found: " + symbol);
793 return absl::InternalError(
"Failed to open file for symbol: " + symbol);
798 return absl::InternalError(
"No active text editor");
802 editor->SelectWordUnderCursor();
803 return absl::OkStatus();
807 if (reference.empty()) {
808 return absl::InvalidArgumentError(
"Reference is empty");
811 if (
auto file_ref = ParseAsmFileLineRef(reference); file_ref.has_value()) {
812 std::filesystem::path root;
819 return absl::FailedPreconditionError(
820 "No code folder loaded (open a folder or set project code_folder)");
827 auto path_or = FindAsmFileInFolder(root, file_ref->file_ref);
828 if (!path_or.has_value()) {
829 return absl::NotFoundError(
"File not found: " + file_ref->file_ref);
834 return absl::InternalError(
"Failed to open file: " + path_or->string());
837 const int line0 = std::max(0, file_ref->line_one_based - 1);
838 const int col0 = std::max(0, file_ref->column_one_based - 1);
841 return absl::InternalError(
"No active text editor");
844 editor->SelectWordUnderCursor();
845 return absl::OkStatus();
848 if (
auto file_ref = ParseAsmFileSymbolRef(reference); file_ref.has_value()) {
849 std::filesystem::path root;
856 return absl::FailedPreconditionError(
857 "No code folder loaded (open a folder or set project code_folder)");
864 auto path_or = FindAsmFileInFolder(root, file_ref->file_ref);
865 if (!path_or.has_value()) {
866 return absl::NotFoundError(
"File not found: " + file_ref->file_ref);
869 auto loc = FindLabelInFile(*path_or, file_ref->symbol);
870 if (!loc.has_value()) {
871 return absl::NotFoundError(absl::StrCat(
872 "Symbol not found in ", file_ref->file_ref,
": ", file_ref->symbol));
877 return absl::InternalError(
"Failed to open file: " + loc->file);
882 return absl::InternalError(
"No active text editor");
885 editor->SelectWordUnderCursor();
886 return absl::OkStatus();
941 if (ImGui::BeginMenuBar()) {
950 const char* file_label =
952 ImGui::Text(tr(
"%6d/%-6d %6d lines | %s | %s | %s | %s"), cpos.mLine + 1,
959 editor->
Render(
"##asm_editor",
960 ImVec2(0, -ImGui::GetFrameHeightWithSpacing()));
977 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
981 ImGui::TextDisabled(tr(
"No folder opened"));
986 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
"%s",
996 ImGui::TextDisabled(tr(
"No symbols loaded."));
999 tr(
"Apply a patch or load external symbols to populate this list."));
1004 static char filter[256] =
"";
1005 ImGui::SetNextItemWidth(-1);
1006 ImGui::InputTextWithHint(
"##symbol_filter",
1012 if (ImGui::BeginChild(
"##symbol_list", ImVec2(0, 0),
false)) {
1013 for (
const auto& [name, symbol] :
symbols_) {
1015 if (filter[0] !=
'\0' && name.find(filter) == std::string::npos) {
1019 ImGui::PushID(name.c_str());
1020 if (ImGui::Selectable(name.c_str())) {
1023 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
1024 ImGui::TextDisabled(
"$%06X", symbol.address);
1033 ImGui::Text(tr(
"Errors: %zu Warnings: %zu"),
last_errors_.size(),
1042 if (has_active_file) {
1051 bool apply_disabled = !has_rom || !has_active_file;
1052 ImGui::BeginDisabled(apply_disabled);
1053 if (ImGui::Button(
ICON_MD_BUILD " Apply to ROM", ImVec2(140, 0))) {
1059 ImGui::EndDisabled();
1060 if (apply_disabled &&
1061 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1063 ImGui::SetTooltip(tr(
"Load a ROM first"));
1065 ImGui::SetTooltip(tr(
"Open an assembly file first"));
1071 if (ImGui::BeginChild(
"##build_log", ImVec2(0, 0),
true)) {
1077 int line,
int column) {
1078 if (!file.empty()) {
1079 std::string reference = file;
1081 reference = absl::StrCat(reference,
":", line);
1083 reference = absl::StrCat(reference,
":", column);
1089 "Failed to open diagnostic location: " +
1090 std::string(status.message()),
1098 column > 0 ? column - 1 : 0);
1099 editor->SetCursorPosition(coords);
1107 ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
1112 ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
1116 ImGui::TextDisabled(tr(
"No build output"));
1126 return (symbol_it->second.address >> 16) & 0xFFu;
1129 if (!parsed.has_value()) {
1130 return std::nullopt;
1132 return (*parsed >> 16) & 0xFFu;
1136 if (
const char* env = std::getenv(
"Z3DISASM_BIN"); env && env[0] !=
'\0') {
1137 return std::string(env);
1141 const auto cwd = std::filesystem::current_path(ec);
1144 FindUpwardPath(cwd, std::filesystem::path(
"scripts") /
"z3disasm");
1145 script.has_value()) {
1146 return script->string();
1161 return (std::filesystem::path(
rom_->
filename()).parent_path() /
"z3disasm")
1165 return (std::filesystem::current_path(ec) /
"z3disasm").string();
1194 return absl::StrFormat(
"project-graph --query=bank --bank=%02X --format=json",
1199 uint32_t address)
const {
1200 return absl::StrFormat(
1201 "project-graph --query=lookup --address=%06X --format=json", address);
1205 const std::vector<std::string>& args,
const std::string& title) {
1207 if (!editor_manager || !editor_manager->right_drawer_manager()) {
1208 return absl::FailedPreconditionError(
1209 "Right drawer manager is unavailable for project-graph results");
1220 auto status = tool.
Run(args,
nullptr, &output);
1235 {
"--query=lookup", absl::StrFormat(
"--address=%06X", address),
1237 absl::StrFormat(
"Project Graph Lookup $%06X", address))
1241 editor_manager->right_drawer_manager()->SetToolOutput(
1242 title, absl::StrJoin(args,
" "), output, std::move(actions));
1243 editor_manager->right_drawer_manager()->OpenDrawer(
1245 return absl::OkStatus();
1257 if (selected_bank < 0) {
1263 const std::string sourcemap_path =
1267 if (LoadJsonFile(sourcemap_path, &sourcemap) &&
1270 std::map<int, std::string> files_by_id;
1271 for (
const auto& file : sourcemap[
"files"]) {
1272 files_by_id[file.value(
"id", -1)] = file.value(
"path",
"");
1275 for (
const auto& entry : sourcemap[
"entries"]) {
1276 const std::string address_str = entry.value(
"address",
"0x0");
1277 auto address = ParseHexAddress(address_str);
1278 if (!address.has_value() ||
1279 NormalizeLoRomBankIndex(*address) != selected_bank) {
1285 jump.
line = entry.value(
"line", 0);
1286 jump.
file = files_by_id[entry.value(
"file_id", -1)];
1287 if (!jump.
file.empty()) {
1293 if (lhs.address != rhs.address) {
1294 return lhs.address < rhs.address;
1297 return lhs.file < rhs.file;
1304 const std::string hooks_path =
1305 !project.z3dk_settings.artifact_paths.hooks_json.
empty()
1306 ? project.z3dk_settings.artifact_paths.hooks_json
1307 : project.GetZ3dkArtifactPath(
"hooks.json");
1308 if (LoadJsonFile(hooks_path, &hooks) && hooks.
contains(
"hooks") &&
1310 for (
const auto& hook : hooks[
"hooks"]) {
1311 const std::string address_str = hook.value(
"address",
"0x0");
1312 auto address = ParseHexAddress(address_str);
1313 if (!address.has_value() ||
1314 NormalizeLoRomBankIndex(*address) != selected_bank) {
1318 Z3DisasmHookJump jump;
1319 jump.address = *address;
1320 jump.size = hook.value(
"size", 0);
1321 jump.kind = hook.value(
"kind",
"patch");
1322 jump.name = hook.value(
"name",
"");
1323 jump.source = hook.value(
"source",
"");
1324 z3disasm_hook_jumps_.push_back(std::move(jump));
1326 std::sort(z3disasm_hook_jumps_.begin(), z3disasm_hook_jumps_.end(),
1327 [](
const Z3DisasmHookJump& lhs,
const Z3DisasmHookJump& rhs) {
1328 if (lhs.address != rhs.address) {
1329 return lhs.address < rhs.address;
1331 return lhs.name < rhs.name;
1336void AssemblyEditor::LoadSelectedZ3DisassemblyFile() {
1337 z3disasm_selected_contents_.clear();
1338 z3disasm_selected_path_.clear();
1339 z3disasm_source_jumps_.clear();
1340 z3disasm_hook_jumps_.clear();
1341 if (z3disasm_selected_index_ < 0 ||
1342 z3disasm_selected_index_ >=
static_cast<int>(z3disasm_files_.size())) {
1346 z3disasm_selected_path_ = z3disasm_files_[z3disasm_selected_index_];
1347 std::ifstream file(z3disasm_selected_path_);
1348 if (!file.is_open()) {
1349 z3disasm_status_ = absl::StrCat(
"Failed to open ", z3disasm_selected_path_);
1352 z3disasm_selected_contents_.assign(std::istreambuf_iterator<char>(file),
1353 std::istreambuf_iterator<char>());
1354 RefreshSelectedZ3DisassemblyMetadata();
1357void AssemblyEditor::RefreshZ3DisassemblyFiles() {
1358 z3disasm_files_.clear();
1359 const std::filesystem::path output_dir(ResolveZ3DisasmOutputDir());
1361 if (!std::filesystem::exists(output_dir, ec) || ec) {
1362 z3disasm_selected_index_ = -1;
1363 z3disasm_selected_path_.clear();
1364 z3disasm_selected_contents_.clear();
1365 z3disasm_source_jumps_.clear();
1366 z3disasm_hook_jumps_.clear();
1370 std::string previous_selection = z3disasm_selected_path_;
1371 for (
const auto& entry :
1372 std::filesystem::directory_iterator(output_dir, ec)) {
1376 if (!entry.is_regular_file()) {
1379 if (IsGeneratedBankFile(entry.path())) {
1380 z3disasm_files_.push_back(entry.path().string());
1383 std::sort(z3disasm_files_.begin(), z3disasm_files_.end());
1385 if (z3disasm_files_.empty()) {
1386 z3disasm_selected_index_ = -1;
1387 z3disasm_selected_path_.clear();
1388 z3disasm_selected_contents_.clear();
1389 z3disasm_source_jumps_.clear();
1390 z3disasm_hook_jumps_.clear();
1394 z3disasm_selected_index_ = 0;
1395 if (!previous_selection.empty()) {
1396 const auto it = std::find(z3disasm_files_.begin(), z3disasm_files_.end(),
1397 previous_selection);
1398 if (it != z3disasm_files_.end()) {
1399 z3disasm_selected_index_ =
1400 static_cast<int>(std::distance(z3disasm_files_.begin(), it));
1403 LoadSelectedZ3DisassemblyFile();
1406void AssemblyEditor::PollZ3DisassemblyTask() {
1407 const auto snapshot = z3disasm_task_.GetSnapshot();
1408 if (!snapshot.started) {
1411 if (snapshot.running) {
1413 absl::StrCat(
"z3disasm running...\n", snapshot.output_tail);
1416 if (z3disasm_task_acknowledged_ || !snapshot.finished) {
1420 z3disasm_task_acknowledged_ =
true;
1421 if (snapshot.status.ok()) {
1422 RefreshZ3DisassemblyFiles();
1423 z3disasm_status_ = absl::StrCat(
1424 "z3disasm finished.",
1425 z3disasm_files_.empty()
1426 ? std::string(
" No bank files were generated.")
1427 : absl::StrFormat(
" Loaded %d bank file(s).",
1428 static_cast<int>(z3disasm_files_.size())));
1431 absl::StrCat(
"z3disasm failed: ", snapshot.status.message(),
"\n",
1432 snapshot.output_tail);
1434 z3disasm_task_.Wait().IgnoreError();
1437absl::Status AssemblyEditor::GenerateZ3Disassembly() {
1438 if (
const auto snapshot = z3disasm_task_.GetSnapshot(); snapshot.running) {
1439 return absl::FailedPreconditionError(
"z3disasm is already running");
1442 const std::string rom_path = ResolveZ3DisasmRomPath();
1443 if (rom_path.empty()) {
1444 return absl::FailedPreconditionError(
1445 "No ROM path is available for z3disasm");
1448 const std::string command_path = ResolveZ3DisasmCommand();
1449 const std::string output_dir = ResolveZ3DisasmOutputDir();
1450 z3disasm_output_dir_ = output_dir;
1453 std::filesystem::create_directories(output_dir, ec);
1454 for (
const auto& entry :
1455 std::filesystem::directory_iterator(output_dir, ec)) {
1456 if (!ec && entry.is_regular_file() && IsGeneratedBankFile(entry.path())) {
1457 std::filesystem::remove(entry.path(), ec);
1461 std::string command = ShellQuote(command_path);
1462 command +=
" --rom ";
1463 command += ShellQuote(rom_path);
1464 command +=
" --out ";
1465 command += ShellQuote(output_dir);
1467 if (!z3disasm_all_banks_) {
1468 command += absl::StrFormat(
" --bank-start %02X --bank-end %02X",
1469 std::clamp(z3disasm_bank_start_, 0, 0xFF),
1470 std::clamp(z3disasm_bank_end_, 0, 0xFF));
1473 if (dependencies_.project) {
1474 const std::string symbols_path =
1475 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb.empty()
1476 ? dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
1477 : dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
1478 if (!symbols_path.empty() && std::filesystem::exists(symbols_path)) {
1479 command +=
" --symbols ";
1480 command += ShellQuote(symbols_path);
1483 const std::string hooks_path =
1484 !dependencies_.project->z3dk_settings.artifact_paths.hooks_json.empty()
1485 ? dependencies_.project->z3dk_settings.artifact_paths.hooks_json
1486 : dependencies_.project->GetZ3dkArtifactPath(
"hooks.json");
1487 if (!hooks_path.empty() && std::filesystem::exists(hooks_path)) {
1488 command +=
" --hooks ";
1489 command += ShellQuote(hooks_path);
1493 z3disasm_task_acknowledged_ =
false;
1494 z3disasm_selected_index_ = -1;
1495 z3disasm_selected_path_.clear();
1496 z3disasm_selected_contents_.clear();
1497 z3disasm_source_jumps_.clear();
1498 z3disasm_hook_jumps_.clear();
1499 z3disasm_files_.clear();
1500 z3disasm_status_ =
"Launching z3disasm...";
1501 return z3disasm_task_.Start(command,
1502 std::filesystem::current_path().
string());
1505absl::Status AssemblyEditor::NavigateDisassemblyQuery() {
1506 auto symbol_it = symbols_.find(disasm_query_);
1507 if (symbol_it != symbols_.end()) {
1508 disasm_query_ = absl::StrCat(
"0x", absl::Hex(symbol_it->second.address));
1509 disasm_status_ = absl::StrCat(
"Resolved symbol ", symbol_it->first);
1510 return absl::OkStatus();
1513 auto parsed = ParseHexAddress(disasm_query_);
1514 if (!parsed.has_value()) {
1515 return absl::InvalidArgumentError(
"Enter a SNES address or known symbol");
1518 disasm_query_ = absl::StrCat(
"0x", absl::Hex(*parsed));
1519 disasm_status_ = absl::StrFormat(
"Showing disassembly at $%06X", *parsed);
1520 return absl::OkStatus();
1523void AssemblyEditor::DrawDisassemblyContent() {
1524 PollZ3DisassemblyTask();
1526 if (z3disasm_output_dir_.empty()) {
1527 z3disasm_output_dir_ = ResolveZ3DisasmOutputDir();
1529 if (z3disasm_files_.empty()) {
1530 RefreshZ3DisassemblyFiles();
1533 ImGui::TextDisabled(
1534 tr(
"z3disasm-backed bank browser for generated `bank_XX.asm` files."));
1535 const std::string rom_path = ResolveZ3DisasmRomPath();
1536 ImGui::TextWrapped(tr(
"ROM: %s"),
1537 rom_path.empty() ?
"<unavailable>" : rom_path.c_str());
1538 ImGui::TextWrapped(tr(
"Output: %s"), z3disasm_output_dir_.c_str());
1540 ImGui::Checkbox(tr(
"All banks"), &z3disasm_all_banks_);
1541 if (!z3disasm_all_banks_) {
1543 ImGui::SetNextItemWidth(70.0f);
1544 ImGui::InputInt(tr(
"Start"), &z3disasm_bank_start_);
1546 ImGui::SetNextItemWidth(70.0f);
1547 ImGui::InputInt(tr(
"End"), &z3disasm_bank_end_);
1548 z3disasm_bank_start_ = std::clamp(z3disasm_bank_start_, 0, 0xFF);
1549 z3disasm_bank_end_ = std::clamp(z3disasm_bank_end_, 0, 0xFF);
1550 if (z3disasm_bank_end_ < z3disasm_bank_start_) {
1551 z3disasm_bank_end_ = z3disasm_bank_start_;
1554 if (ImGui::SmallButton(tr(
"Use Query Bank"))) {
1555 if (
auto bank = CurrentDisassemblyBank(); bank.has_value()) {
1556 z3disasm_bank_start_ =
static_cast<int>(*bank);
1557 z3disasm_bank_end_ =
static_cast<int>(*bank);
1562 const auto task_snapshot = z3disasm_task_.GetSnapshot();
1563 const bool can_generate = !rom_path.empty();
1564 ImGui::BeginDisabled(!can_generate || task_snapshot.running);
1566 auto status = GenerateZ3Disassembly();
1568 z3disasm_status_ = std::string(status.message());
1569 if (dependencies_.toast_manager) {
1570 dependencies_.toast_manager->Show(z3disasm_status_, ToastType::kError);
1574 ImGui::EndDisabled();
1575 if (task_snapshot.running) {
1578 z3disasm_task_.Cancel();
1581 if (!can_generate) {
1583 ImGui::TextDisabled(tr(
"Load or configure a ROM path first."));
1586 if (!z3disasm_status_.empty()) {
1588 ImGui::TextWrapped(
"%s", z3disasm_status_.c_str());
1591 if (!task_snapshot.output_tail.empty()) {
1592 if (ImGui::CollapsingHeader(tr(
"z3disasm Output"))) {
1593 std::string output_tail = task_snapshot.output_tail;
1594 ImGui::InputTextMultiline(
"##z3disasm_output", &output_tail,
1595 ImVec2(-1.0f, 80.0f),
1596 ImGuiInputTextFlags_ReadOnly);
1600 ImGui::SeparatorText(tr(
"Bank Browser"));
1601 if (z3disasm_files_.empty()) {
1602 ImGui::TextDisabled(tr(
1603 "No generated bank files yet. Run z3disasm to populate this browser."));
1605 if (ImGui::BeginTable(
1606 "##z3disasm_browser", 2,
1607 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
1608 ImGui::TableSetupColumn(
"Banks", ImGuiTableColumnFlags_WidthFixed,
1610 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch);
1611 ImGui::TableNextRow();
1613 ImGui::TableSetColumnIndex(0);
1614 if (ImGui::BeginChild(
"##z3disasm_list", ImVec2(0, 0),
true)) {
1615 for (
int i = 0; i < static_cast<int>(z3disasm_files_.size()); ++i) {
1616 const std::string name =
1617 std::filesystem::path(z3disasm_files_[i]).filename().string();
1618 if (ImGui::Selectable(name.c_str(), z3disasm_selected_index_ == i)) {
1619 z3disasm_selected_index_ = i;
1620 LoadSelectedZ3DisassemblyFile();
1626 ImGui::TableSetColumnIndex(1);
1627 if (ImGui::BeginChild(
"##z3disasm_preview", ImVec2(0, 0),
true)) {
1628 if (!z3disasm_selected_path_.empty()) {
1629 const std::string selected_name =
1630 std::filesystem::path(z3disasm_selected_path_)
1633 ImGui::TextUnformatted(selected_name.c_str());
1635 if (ImGui::SmallButton(tr(
"Open in Code Editor"))) {
1636 ChangeActiveFile(z3disasm_selected_path_);
1638 const std::string bank_query = BuildProjectGraphBankQuery();
1639 if (!bank_query.empty()) {
1641 if (ImGui::SmallButton(tr(
"Open Bank Graph"))) {
1642 auto status = RunProjectGraphQueryInDrawer(
1644 absl::StrFormat(
"--bank=%02X", SelectedZ3DisasmBankIndex()),
1646 absl::StrFormat(
"Project Graph Bank $%02X",
1647 SelectedZ3DisasmBankIndex()));
1649 z3disasm_status_ = std::string(status.message());
1653 if (ImGui::SmallButton(tr(
"Copy Bank Query"))) {
1654 ImGui::SetClipboardText(bank_query.c_str());
1656 ImGui::TextDisabled(
"%s", bank_query.c_str());
1659 if (!z3disasm_source_jumps_.empty() &&
1660 ImGui::CollapsingHeader(tr(
"Source Map Jumps"),
1661 ImGuiTreeNodeFlags_DefaultOpen)) {
1662 for (
const auto& jump : z3disasm_source_jumps_) {
1663 ImGui::PushID(
static_cast<int>(jump.address) ^ jump.line);
1664 const std::string source_label =
1665 absl::StrFormat(
"Source##source_jump_%06X", jump.address);
1666 const std::string graph_label =
1667 absl::StrFormat(
"Graph##source_graph_%06X", jump.address);
1668 const std::string copy_label =
1669 absl::StrFormat(
"Copy##source_copy_%06X", jump.address);
1670 const std::string addr_label =
1671 absl::StrFormat(
"Addr##source_addr_%06X", jump.address);
1672 if (ImGui::SmallButton(source_label.c_str())) {
1673 JumpToReference(absl::StrCat(jump.file,
":", jump.line))
1677 if (ImGui::SmallButton(graph_label.c_str())) {
1678 auto status = RunProjectGraphQueryInDrawer(
1680 absl::StrFormat(
"--address=%06X", jump.address),
1682 absl::StrFormat(
"Project Graph Lookup $%06X",
1685 z3disasm_status_ = std::string(status.message());
1689 if (ImGui::SmallButton(copy_label.c_str())) {
1690 const std::string query =
1691 BuildProjectGraphLookupQuery(jump.address);
1692 ImGui::SetClipboardText(query.c_str());
1695 if (ImGui::SmallButton(addr_label.c_str())) {
1696 disasm_query_ = absl::StrFormat(
"0x%06X", jump.address);
1697 NavigateDisassemblyQuery().IgnoreError();
1700 ImGui::TextWrapped(
"$%06X %s:%d", jump.address,
1701 jump.file.c_str(), jump.line);
1705 if (!z3disasm_hook_jumps_.empty() &&
1706 ImGui::CollapsingHeader(tr(
"Hook Jumps"),
1707 ImGuiTreeNodeFlags_DefaultOpen)) {
1708 for (
const auto& hook : z3disasm_hook_jumps_) {
1709 ImGui::PushID(
static_cast<int>(hook.address) ^ hook.size ^
1711 const std::string source_label =
1712 absl::StrFormat(
"Source##hook_jump_%06X", hook.address);
1713 const std::string graph_label =
1714 absl::StrFormat(
"Graph##hook_graph_%06X", hook.address);
1715 const std::string copy_label =
1716 absl::StrFormat(
"Copy##hook_copy_%06X", hook.address);
1717 const std::string addr_label =
1718 absl::StrFormat(
"Addr##hook_addr_%06X", hook.address);
1719 if (!hook.source.empty() &&
1720 ImGui::SmallButton(source_label.c_str())) {
1721 JumpToReference(hook.source).IgnoreError();
1723 if (!hook.source.empty()) {
1726 if (ImGui::SmallButton(graph_label.c_str())) {
1727 auto status = RunProjectGraphQueryInDrawer(
1729 absl::StrFormat(
"--address=%06X", hook.address),
1731 absl::StrFormat(
"Project Graph Lookup $%06X",
1734 z3disasm_status_ = std::string(status.message());
1738 if (ImGui::SmallButton(copy_label.c_str())) {
1739 const std::string query =
1740 BuildProjectGraphLookupQuery(hook.address);
1741 ImGui::SetClipboardText(query.c_str());
1744 if (ImGui::SmallButton(addr_label.c_str())) {
1745 disasm_query_ = absl::StrFormat(
"0x%06X", hook.address);
1746 NavigateDisassemblyQuery().IgnoreError();
1749 ImGui::TextWrapped(
"$%06X %s %s (+%d) %s", hook.address,
1750 hook.kind.c_str(), hook.name.c_str(),
1751 hook.size, hook.source.c_str());
1756 ImGui::InputTextMultiline(
1757 "##z3disasm_text", &z3disasm_selected_contents_,
1758 ImVec2(-1.0f, -1.0f), ImGuiInputTextFlags_ReadOnly);
1760 ImGui::TextDisabled(
1761 tr(
"Select a generated bank file to preview it."));
1769 ImGui::SeparatorText(tr(
"Quick ROM Slice"));
1770 ImGui::TextDisabled(tr(
"Inline viewer for the currently loaded ROM buffer."));
1771 ImGui::SetNextItemWidth(220.0f);
1772 ImGui::InputText(tr(
"Address or Symbol"), &disasm_query_);
1774 if (ImGui::Button(tr(
"Go"))) {
1775 auto status = NavigateDisassemblyQuery();
1777 disasm_status_ = std::string(status.message());
1781 ImGui::SetNextItemWidth(80.0f);
1782 ImGui::InputInt(tr(
"Count"), &disasm_instruction_count_);
1783 if (disasm_instruction_count_ < 1) {
1784 disasm_instruction_count_ = 1;
1786 if (disasm_instruction_count_ > 128) {
1787 disasm_instruction_count_ = 128;
1790 if (!disasm_status_.empty()) {
1791 ImGui::TextDisabled(
"%s", disasm_status_.c_str());
1795 if (!rom_ || !rom_->is_loaded()) {
1796 ImGui::TextDisabled(tr(
"Load a ROM to browse disassembly."));
1800 uint32_t start_address = 0;
1801 if (
auto symbol_it = symbols_.find(disasm_query_);
1802 symbol_it != symbols_.end()) {
1803 start_address = symbol_it->second.address;
1805 auto parsed = ParseHexAddress(disasm_query_);
1806 if (!parsed.has_value()) {
1807 ImGui::TextDisabled(
1808 tr(
"Enter a SNES address like 0x008000 or a known label."));
1811 start_address = *parsed;
1814 std::map<uint32_t, std::string> symbols_by_address;
1815 for (
const auto& [name, symbol] : symbols_) {
1816 symbols_by_address.emplace(symbol.address, name);
1821 auto it = symbols_by_address.find(address);
1822 return it == symbols_by_address.end() ? std::string() : it->second;
1825 auto read_byte = [
this](uint32_t snes_addr) -> uint8_t {
1826 if (!rom_ || !rom_->is_loaded()) {
1829 const uint32_t pc_addr =
SnesToPc(snes_addr);
1830 if (pc_addr >= rom_->size()) {
1833 return rom_->vector()[pc_addr];
1837 start_address,
static_cast<size_t>(disasm_instruction_count_), read_byte);
1838 if (instructions.empty()) {
1839 ImGui::TextDisabled(tr(
"No disassembly available for this address."));
1843 if (ImGui::BeginTable(
"##assembly_disasm", 3,
1844 ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
1845 ImGuiTableFlags_Resizable)) {
1846 ImGui::TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed,
1848 ImGui::TableSetupColumn(
"Instruction", ImGuiTableColumnFlags_WidthStretch);
1849 ImGui::TableSetupColumn(
"Context", ImGuiTableColumnFlags_WidthFixed,
1851 ImGui::TableHeadersRow();
1853 for (
const auto& instruction : instructions) {
1854 ImGui::PushID(
static_cast<int>(instruction.address));
1855 ImGui::TableNextRow();
1857 ImGui::TableSetColumnIndex(0);
1858 const std::string address_label =
1859 absl::StrFormat(
"$%06X", instruction.address);
1860 ImGui::TextUnformatted(address_label.c_str());
1862 ImGui::TableSetColumnIndex(1);
1863 if (
auto label_it = symbols_by_address.find(instruction.address);
1864 label_it != symbols_by_address.end()) {
1865 ImGui::TextColored(ImVec4(0.75f, 0.85f, 1.0f, 1.0f),
1866 "%s:", label_it->second.c_str());
1868 ImGui::TextWrapped(
"%s", instruction.full_text.c_str());
1870 ImGui::TableSetColumnIndex(2);
1871 if (instruction.branch_target != 0) {
1872 auto target_it = symbols_by_address.find(instruction.branch_target);
1873 if (target_it != symbols_by_address.end()) {
1874 if (ImGui::SmallButton(target_it->second.c_str())) {
1875 JumpToSymbolDefinition(target_it->second).IgnoreError();
1878 ImGui::Text(
"$%06X", instruction.branch_target);
1881 ImGui::TextDisabled(
"-");
1889void AssemblyEditor::DrawToolbarContent() {
1890 float button_size = 32.0f;
1893 auto folder = FileDialogWrapper::ShowOpenFolderDialog();
1894 if (!folder.empty()) {
1895 current_folder_ = LoadFolder(folder);
1898 if (ImGui::IsItemHovered())
1899 ImGui::SetTooltip(tr(
"Open Folder"));
1903 auto filename = FileDialogWrapper::ShowOpenFileDialog();
1904 if (!filename.empty()) {
1905 ChangeActiveFile(filename);
1908 if (ImGui::IsItemHovered())
1909 ImGui::SetTooltip(tr(
"Open File"));
1912 bool can_save = HasActiveFile();
1913 ImGui::BeginDisabled(!can_save);
1914 if (ImGui::Button(
ICON_MD_SAVE, ImVec2(button_size, button_size))) {
1917 ImGui::EndDisabled();
1918 if (ImGui::IsItemHovered())
1919 ImGui::SetTooltip(tr(
"Save File"));
1926 ImGui::BeginDisabled(!can_save);
1928 ValidateCurrentFile();
1930 ImGui::EndDisabled();
1931 if (ImGui::IsItemHovered())
1932 ImGui::SetTooltip(tr(
"Validate (Ctrl+B)"));
1935 bool can_apply = can_save && rom_ && rom_->is_loaded();
1936 ImGui::BeginDisabled(!can_apply);
1937 if (ImGui::Button(
ICON_MD_BUILD, ImVec2(button_size, button_size))) {
1940 ImGui::EndDisabled();
1941 if (ImGui::IsItemHovered())
1942 ImGui::SetTooltip(tr(
"Apply to ROM (Ctrl+Shift+B)"));
1945void AssemblyEditor::DrawFileTabView() {
1946 if (active_files_.empty()) {
1951 ImGuiTabBarFlags_Reorderable |
1952 ImGuiTabBarFlags_AutoSelectNewTabs |
1953 ImGuiTabBarFlags_FittingPolicyScroll)) {
1954 for (
int i = 0; i < active_files_.Size; i++) {
1955 int file_id = active_files_[i];
1956 if (file_id >= files_.size()) {
1961 std::string filename = files_[file_id];
1962 size_t pos = filename.find_last_of(
"/\\");
1963 if (pos != std::string::npos) {
1964 filename = filename.substr(pos + 1);
1967 bool is_active = (active_file_id_ == file_id);
1968 ImGuiTabItemFlags flags = is_active ? ImGuiTabItemFlags_SetSelected : 0;
1969 bool tab_open =
true;
1971 if (ImGui::BeginTabItem(filename.c_str(), &tab_open, flags)) {
1974 active_file_id_ = file_id;
1977 ImGui::EndTabItem();
1982 active_files_.erase(active_files_.Data + i);
1983 if (active_file_id_ == file_id) {
1984 active_file_id_ = active_files_.empty() ? -1 : active_files_[0];
1985 if (active_file_id_ >= 0 &&
1986 active_file_id_ <
static_cast<int>(open_files_.size())) {
1989 current_file_.clear();
2003absl::Status AssemblyEditor::Update() {
2005 return absl::OkStatus();
2008 if (dependencies_.window_manager !=
nullptr) {
2009 return absl::OkStatus();
2014 ImGui::Begin(
"Assembly Editor", &active_, ImGuiWindowFlags_MenuBar);
2021 return absl::OkStatus();
2024void AssemblyEditor::InlineUpdate() {
2027 const char* file_label =
2028 current_file_.empty() ?
"No file" : current_file_.c_str();
2029 ImGui::Text(tr(
"%6d/%-6d %6d lines | %s | %s | %s | %s"), cpos.mLine + 1,
2032 editor->
CanUndo() ?
"*" :
" ",
2035 editor->
Render(
"##asm_editor", ImVec2(0, 0));
2038void AssemblyEditor::UpdateCodeView() {
2041 DrawToolbarContent();
2046absl::Status AssemblyEditor::Save() {
2047 if (!HasActiveFile()) {
2048 return absl::FailedPreconditionError(
"No active file to save.");
2051 const std::string& path = files_[active_file_id_];
2052 std::ofstream file(path);
2053 if (!file.is_open()) {
2054 return absl::InvalidArgumentError(
2055 absl::StrCat(
"Cannot write file: ", path));
2058 file << GetActiveEditor()->GetText();
2060 return absl::OkStatus();
2063void AssemblyEditor::DrawToolset() {
2068 current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
2077void AssemblyEditor::DrawCurrentFolder() {
2079 if (current_folder_.name.empty() && dependencies_.project &&
2080 !dependencies_.project->code_folder.empty()) {
2081 OpenFolder(dependencies_.project->GetAbsolutePath(
2082 dependencies_.project->code_folder));
2085 if (ImGui::BeginChild(
"##current_folder", ImVec2(0, 0),
true,
2086 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
2087 if (ImGui::BeginTable(
"##file_table", 2,
2088 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
2089 ImGuiTableFlags_Resizable |
2090 ImGuiTableFlags_Sortable)) {
2091 ImGui::TableSetupColumn(
"Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
2092 ImGui::TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthStretch);
2094 ImGui::TableHeadersRow();
2096 for (
const auto& file : current_folder_.files) {
2097 ImGui::TableNextRow();
2098 ImGui::TableNextColumn();
2099 if (ImGui::Selectable(file.c_str())) {
2100 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/", file));
2102 ImGui::TableNextColumn();
2103 ImGui::Text(tr(
"File"));
2106 for (
const auto& subfolder : current_folder_.subfolders) {
2107 ImGui::TableNextRow();
2108 ImGui::TableNextColumn();
2109 if (ImGui::TreeNode(subfolder.name.c_str())) {
2110 for (
const auto& file : subfolder.files) {
2111 ImGui::TableNextRow();
2112 ImGui::TableNextColumn();
2113 if (ImGui::Selectable(file.c_str())) {
2114 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/",
2115 subfolder.name,
"/", file));
2117 ImGui::TableNextColumn();
2118 ImGui::Text(tr(
"File"));
2122 ImGui::TableNextColumn();
2123 ImGui::Text(tr(
"Folder"));
2133void AssemblyEditor::DrawFileMenu() {
2134 if (ImGui::BeginMenu(tr(
"File"))) {
2137 if (!filename.empty()) {
2138 ChangeActiveFile(filename);
2148void AssemblyEditor::DrawEditMenu() {
2149 if (ImGui::BeginMenu(tr(
"Edit"))) {
2151 GetActiveEditor()->Undo();
2154 GetActiveEditor()->Redo();
2158 GetActiveEditor()->Cut();
2161 GetActiveEditor()->Copy();
2164 GetActiveEditor()->Paste();
2174void AssemblyEditor::ChangeActiveFile(
const std::string_view& filename) {
2175 if (filename.empty()) {
2180 for (
int i = 0; i < active_files_.Size; ++i) {
2181 int file_id = active_files_[i];
2182 if (files_[file_id] == filename) {
2184 active_file_id_ = file_id;
2193 int new_file_id = files_.size();
2194 files_.push_back(std::string(filename));
2195 active_files_.push_back(new_file_id);
2198 if (new_file_id >= open_files_.size()) {
2199 open_files_.resize(new_file_id + 1);
2202 open_files_[new_file_id].SetText(content);
2203 open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
2205 open_files_[new_file_id].SetShowWhitespaces(
false);
2206 active_file_id_ = new_file_id;
2208 }
catch (
const std::exception& ex) {
2209 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error opening file: %s\n",
2214absl::Status AssemblyEditor::Cut() {
2215 GetActiveEditor()->Cut();
2216 return absl::OkStatus();
2219absl::Status AssemblyEditor::Copy() {
2220 GetActiveEditor()->Copy();
2221 return absl::OkStatus();
2224absl::Status AssemblyEditor::Paste() {
2225 GetActiveEditor()->Paste();
2226 return absl::OkStatus();
2229absl::Status AssemblyEditor::Undo() {
2230 GetActiveEditor()->Undo();
2231 return absl::OkStatus();
2234absl::Status AssemblyEditor::Redo() {
2235 GetActiveEditor()->Redo();
2236 return absl::OkStatus();
2239#ifdef YAZE_WITH_Z3DK
2242 if (!dependencies_.project) {
2246 const auto&
z3dk = dependencies_.project->z3dk_settings;
2261 for (
const auto& range :
z3dk.prohibited_memory_ranges) {
2263 {.start = range.start, .end = range.end, .reason = range.reason});
2266 auto append_unique = [&options](
const std::string& path) {
2276 append_unique(dependencies_.project->code_folder);
2277 if (HasActiveFile()) {
2279 std::filesystem::path(files_[active_file_id_]).parent_path().
string());
2282 if (rom_ && rom_->is_loaded() && !rom_->filename().empty()) {
2284 }
else if (!
z3dk.rom_path.empty()) {
2291void AssemblyEditor::ExportZ3dkArtifacts(
const core::AsarPatchResult& result,
2292 bool sync_mesen_symbols) {
2293 if (!dependencies_.project) {
2297 const auto& project = *dependencies_.project;
2298 const auto& configured = project.z3dk_settings.artifact_paths;
2299 const std::string symbols_path =
2300 configured.symbols_mlb.empty()
2301 ? project.GetZ3dkArtifactPath(
"symbols.mlb")
2302 : configured.symbols_mlb;
2303 const std::string sourcemap_path =
2304 configured.sourcemap_json.empty()
2305 ? project.GetZ3dkArtifactPath(
"sourcemap.json")
2306 : configured.sourcemap_json;
2307 const std::string annotations_path =
2308 configured.annotations_json.empty()
2309 ? project.GetZ3dkArtifactPath(
"annotations.json")
2310 : configured.annotations_json;
2311 const std::string hooks_path = configured.hooks_json.empty()
2312 ? project.GetZ3dkArtifactPath(
"hooks.json")
2313 : configured.hooks_json;
2314 const std::string lint_path = configured.lint_json.empty()
2315 ? project.GetZ3dkArtifactPath(
"lint.json")
2316 : configured.lint_json;
2317 auto write_artifact = [](
const std::string& path,
2318 const std::string& content) {
2319 if (path.empty() || content.empty()) {
2323 std::filesystem::create_directories(
2324 std::filesystem::path(path).parent_path(), ec);
2325 std::ofstream file(path, std::ios::binary | std::ios::trunc);
2326 if (!file.is_open()) {
2332 write_artifact(symbols_path, result.symbols_mlb);
2333 write_artifact(sourcemap_path, result.sourcemap_json);
2334 write_artifact(annotations_path, result.annotations_json);
2335 write_artifact(hooks_path, result.hooks_json);
2336 write_artifact(lint_path, result.lint_json);
2338 if (!sync_mesen_symbols || symbols_path.empty() ||
2339 result.symbols_mlb.empty()) {
2344 if (!client->IsConnected()) {
2345 client->Connect().IgnoreError();
2347 if (!client->IsConnected()) {
2351 auto status = client->LoadSymbolsFile(symbols_path);
2353 if (dependencies_.toast_manager) {
2354 dependencies_.toast_manager->Show(
2355 "Failed to sync Mesen2 symbols: " + std::string(status.message()),
2356 ToastType::kWarning);
2358 }
else if (dependencies_.toast_manager) {
2359 dependencies_.toast_manager->Show(
2360 "Synced symbols to Mesen2 from " + symbols_path, ToastType::kSuccess);
2369absl::Status AssemblyEditor::ValidateCurrentFile() {
2370 if (!HasActiveFile()) {
2371 return absl::FailedPreconditionError(
"No file is currently active");
2374 const std::string& file_path = files_[active_file_id_];
2376#ifdef YAZE_WITH_Z3DK
2377 const auto options = BuildZ3dkAssembleOptions();
2381 std::vector<uint8_t> scratch;
2382 auto result_or = z3dk_.ApplyPatch(file_path, scratch, options);
2383 if (!result_or.ok()) {
2384 last_errors_.clear();
2385 last_errors_.push_back(std::string(result_or.status().message()));
2386 last_warnings_.clear();
2387 last_diagnostics_.clear();
2389 GetActiveEditor()->SetErrorMarkers(empty_markers);
2390 return result_or.status();
2392 UpdateErrorMarkers(*result_or);
2393 if (!result_or->success) {
2394 return absl::InternalError(
"Assembly validation failed");
2396 ExportZ3dkArtifacts(*result_or,
false);
2397 ClearErrorMarkers();
2398 return absl::OkStatus();
2401 if (!asar_initialized_) {
2402 auto status = asar_.Initialize();
2406 asar_initialized_ =
true;
2410 auto status = asar_.ValidateAssembly(file_path);
2415 last_errors_.clear();
2416 last_errors_.push_back(std::string(status.message()));
2420 for (
const auto& error : last_errors_) {
2422 size_t line_pos = error.find(
':');
2423 if (line_pos != std::string::npos) {
2424 size_t num_start = line_pos + 1;
2425 size_t num_end = error.find(
':', num_start);
2426 if (num_end != std::string::npos) {
2427 std::string line_str = error.substr(num_start, num_end - num_start);
2429 int line = std::stoi(line_str);
2430 markers[line] = error;
2437 GetActiveEditor()->SetErrorMarkers(markers);
2442 ClearErrorMarkers();
2443 return absl::OkStatus();
2447absl::Status AssemblyEditor::ApplyPatchToRom() {
2448 if (!rom_ || !rom_->is_loaded()) {
2449 return absl::FailedPreconditionError(
"No ROM is loaded");
2452 if (!HasActiveFile()) {
2453 return absl::FailedPreconditionError(
"No file is currently active");
2456 const std::string& file_path = files_[active_file_id_];
2457 std::vector<uint8_t> rom_data = rom_->vector();
2459#ifdef YAZE_WITH_Z3DK
2460 const auto options = BuildZ3dkAssembleOptions();
2461 auto result = z3dk_.ApplyPatch(file_path, rom_data, options);
2463 last_errors_.clear();
2464 last_errors_.push_back(std::string(result.status().message()));
2465 last_warnings_.clear();
2466 last_diagnostics_.clear();
2468 GetActiveEditor()->SetErrorMarkers(empty_markers);
2469 return result.status();
2471 UpdateErrorMarkers(*result);
2472 if (!result->success) {
2473 return absl::InternalError(
"Patch application failed");
2475 rom_->LoadFromData(rom_data);
2476 symbols_ = z3dk_.GetSymbolTable();
2477 ExportZ3dkArtifacts(*result,
true);
2478 ClearErrorMarkers();
2479 return absl::OkStatus();
2482 if (!asar_initialized_) {
2483 auto status = asar_.Initialize();
2487 asar_initialized_ =
true;
2491 auto result = asar_.ApplyPatch(file_path, rom_data);
2494 UpdateErrorMarkers(*result);
2495 return result.status();
2498 if (result->success) {
2500 rom_->LoadFromData(rom_data);
2503 symbols_ = asar_.GetSymbolTable();
2504 last_errors_.clear();
2505 last_warnings_ = result->warnings;
2508 ClearErrorMarkers();
2510 return absl::OkStatus();
2512 UpdateErrorMarkers(*result);
2513 return absl::InternalError(
"Patch application failed");
2519 last_errors_ = result.
errors;
2523 if (!HasActiveFile()) {
2537 markers[d.line] = d.message;
2542 for (
const auto& error : result.
errors) {
2544 size_t first_colon = error.find(
':');
2545 if (first_colon != std::string::npos) {
2546 size_t second_colon = error.find(
':', first_colon + 1);
2547 if (second_colon != std::string::npos) {
2548 std::string line_str =
2549 error.substr(first_colon + 1, second_colon - (first_colon + 1));
2550 int line = std::stoi(line_str);
2551 markers[line] = error;
2560 GetActiveEditor()->SetErrorMarkers(markers);
2563void AssemblyEditor::ClearErrorMarkers() {
2564 last_errors_.clear();
2565 last_diagnostics_.clear();
2567 if (!HasActiveFile()) {
2572 GetActiveEditor()->SetErrorMarkers(empty_markers);
2575void AssemblyEditor::DrawAssembleMenu() {
2576 if (ImGui::BeginMenu(tr(
"Assemble"))) {
2577 bool has_active_file = HasActiveFile();
2578 bool has_rom = (rom_ && rom_->is_loaded());
2582 auto status = ValidateCurrentFile();
2588 if (ImGui::MenuItem(
ICON_MD_BUILD " Apply to ROM",
"Ctrl+Shift+B",
false,
2589 has_active_file && has_rom)) {
2590 auto status = ApplyPatchToRom();
2598 if (dependencies_.project) {
2599 std::string sym_file = dependencies_.project->symbols_filename;
2600 if (dependencies_.project->HasZ3dkConfig() &&
2601 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
2604 dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb;
2605 }
else if (sym_file.empty() || !std::filesystem::exists(sym_file)) {
2606 sym_file = dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
2608 if (!sym_file.empty()) {
2609 std::string abs_path = sym_file;
2610 if (!std::filesystem::path(abs_path).is_absolute()) {
2611 abs_path = dependencies_.project->GetAbsolutePath(sym_file);
2613 auto status = asar_.LoadSymbolsFromFile(abs_path);
2616 symbols_ = asar_.GetSymbolTable();
2617 if (dependencies_.toast_manager) {
2618 dependencies_.toast_manager->Show(
2619 "Successfully loaded external symbols from " + sym_file,
2620 ToastType::kSuccess);
2623 if (dependencies_.toast_manager) {
2624 dependencies_.toast_manager->Show(
2625 "Failed to load symbols: " + std::string(status.message()),
2630 if (dependencies_.toast_manager) {
2631 dependencies_.toast_manager->Show(
2632 "Project does not specify a symbols file.",
2633 ToastType::kWarning);
2641 if (ImGui::MenuItem(
ICON_MD_LIST " Show Symbols",
nullptr,
2642 show_symbol_panel_)) {
2643 show_symbol_panel_ = !show_symbol_panel_;
2649 ImGui::TextDisabled(tr(
"Errors: %zu, Warnings: %zu"), last_errors_.size(),
2650 last_warnings_.size());
2655 if (ImGui::BeginMenu(tr(
"Version"))) {
2656 bool has_version_manager = (dependencies_.version_manager !=
nullptr);
2658 has_version_manager)) {
2659 if (has_version_manager) {
2660 ImGui::OpenPopup(
"Create Snapshot");
2665 if (ImGui::BeginPopupModal(
"Create Snapshot",
nullptr,
2666 ImGuiWindowFlags_AlwaysAutoResize)) {
2667 static char message[256] =
"";
2668 ImGui::InputText(tr(
"Message"), message,
sizeof(message));
2670 if (ImGui::Button(tr(
"Create"), ImVec2(120, 0))) {
2671 auto result = dependencies_.version_manager->CreateSnapshot(message);
2672 if (result.ok() && result->success) {
2673 if (dependencies_.toast_manager) {
2674 dependencies_.toast_manager->Show(
2675 "Snapshot Created: " + result->commit_hash,
2676 ToastType::kSuccess);
2679 if (dependencies_.toast_manager) {
2680 std::string err = result.ok()
2682 : std::string(result.status().message());
2683 dependencies_.toast_manager->Show(
"Snapshot Failed: " + err,
2687 ImGui::CloseCurrentPopup();
2691 if (ImGui::Button(tr(
"Cancel"), ImVec2(120, 0))) {
2692 ImGui::CloseCurrentPopup();
2701void AssemblyEditor::DrawSymbolPanel() {
2702 if (!show_symbol_panel_) {
2706 ImGui::SetNextWindowSize(ImVec2(350, 400), ImGuiCond_FirstUseEver);
2707 if (ImGui::Begin(
"Symbols", &show_symbol_panel_)) {
2708 if (symbols_.empty()) {
2709 ImGui::TextDisabled(tr(
"No symbols loaded."));
2710 ImGui::TextDisabled(tr(
"Apply a patch to load symbols."));
2713 static char filter[256] =
"";
2714 ImGui::InputTextWithHint(
"##symbol_filter",
"Filter symbols...", filter,
2719 if (ImGui::BeginChild(
"##symbol_list", ImVec2(0, 0),
true)) {
2720 for (
const auto& [name, symbol] : symbols_) {
2722 if (filter[0] !=
'\0' && name.find(filter) == std::string::npos) {
2726 ImGui::PushID(name.c_str());
2727 if (ImGui::Selectable(name.c_str())) {
2731 ImGui::SameLine(200);
2732 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.
project::YazeProject * project() const
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
std::string sourcemap_json
Z3dkArtifactPaths artifact_paths