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"));
995 ImGui::TextDisabled(tr(
"No symbols loaded."));
998 tr(
"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(tr(
"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);
1058 ImGui::EndDisabled();
1059 if (apply_disabled &&
1060 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1062 ImGui::SetTooltip(tr(
"Load a ROM first"));
1064 ImGui::SetTooltip(tr(
"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);
1113 ImGui::TextDisabled(tr(
"No build output"));
1123 return (symbol_it->second.address >> 16) & 0xFFu;
1126 if (!parsed.has_value()) {
1127 return std::nullopt;
1129 return (*parsed >> 16) & 0xFFu;
1133 if (
const char* env = std::getenv(
"Z3DISASM_BIN"); env && env[0] !=
'\0') {
1134 return std::string(env);
1138 const auto cwd = std::filesystem::current_path(ec);
1141 FindUpwardPath(cwd, std::filesystem::path(
"scripts") /
"z3disasm");
1142 script.has_value()) {
1143 return script->string();
1158 return (std::filesystem::path(
rom_->
filename()).parent_path() /
"z3disasm")
1162 return (std::filesystem::current_path(ec) /
"z3disasm").string();
1191 return absl::StrFormat(
"project-graph --query=bank --bank=%02X --format=json",
1196 uint32_t address)
const {
1197 return absl::StrFormat(
1198 "project-graph --query=lookup --address=%06X --format=json", address);
1202 const std::vector<std::string>& args,
const std::string& title) {
1204 if (!editor_manager || !editor_manager->right_drawer_manager()) {
1205 return absl::FailedPreconditionError(
1206 "Right drawer manager is unavailable for project-graph results");
1217 auto status = tool.
Run(args,
nullptr, &output);
1232 {
"--query=lookup", absl::StrFormat(
"--address=%06X", address),
1234 absl::StrFormat(
"Project Graph Lookup $%06X", address))
1238 editor_manager->right_drawer_manager()->SetToolOutput(
1239 title, absl::StrJoin(args,
" "), output, std::move(actions));
1240 editor_manager->right_drawer_manager()->OpenDrawer(
1242 return absl::OkStatus();
1254 if (selected_bank < 0) {
1260 const std::string sourcemap_path =
1264 if (LoadJsonFile(sourcemap_path, &sourcemap) &&
1267 std::map<int, std::string> files_by_id;
1268 for (
const auto& file : sourcemap[
"files"]) {
1269 files_by_id[file.value(
"id", -1)] = file.value(
"path",
"");
1272 for (
const auto& entry : sourcemap[
"entries"]) {
1273 const std::string address_str = entry.value(
"address",
"0x0");
1274 auto address = ParseHexAddress(address_str);
1275 if (!address.has_value() ||
1276 NormalizeLoRomBankIndex(*address) != selected_bank) {
1282 jump.
line = entry.value(
"line", 0);
1283 jump.
file = files_by_id[entry.value(
"file_id", -1)];
1284 if (!jump.
file.empty()) {
1290 if (lhs.address != rhs.address) {
1291 return lhs.address < rhs.address;
1294 return lhs.file < rhs.file;
1301 const std::string hooks_path =
1302 !project.z3dk_settings.artifact_paths.hooks_json.
empty()
1303 ? project.z3dk_settings.artifact_paths.hooks_json
1304 : project.GetZ3dkArtifactPath(
"hooks.json");
1305 if (LoadJsonFile(hooks_path, &hooks) && hooks.
contains(
"hooks") &&
1307 for (
const auto& hook : hooks[
"hooks"]) {
1308 const std::string address_str = hook.value(
"address",
"0x0");
1309 auto address = ParseHexAddress(address_str);
1310 if (!address.has_value() ||
1311 NormalizeLoRomBankIndex(*address) != selected_bank) {
1315 Z3DisasmHookJump jump;
1316 jump.address = *address;
1317 jump.size = hook.value(
"size", 0);
1318 jump.kind = hook.value(
"kind",
"patch");
1319 jump.name = hook.value(
"name",
"");
1320 jump.source = hook.value(
"source",
"");
1321 z3disasm_hook_jumps_.push_back(std::move(jump));
1323 std::sort(z3disasm_hook_jumps_.begin(), z3disasm_hook_jumps_.end(),
1324 [](
const Z3DisasmHookJump& lhs,
const Z3DisasmHookJump& rhs) {
1325 if (lhs.address != rhs.address) {
1326 return lhs.address < rhs.address;
1328 return lhs.name < rhs.name;
1333void AssemblyEditor::LoadSelectedZ3DisassemblyFile() {
1334 z3disasm_selected_contents_.clear();
1335 z3disasm_selected_path_.clear();
1336 z3disasm_source_jumps_.clear();
1337 z3disasm_hook_jumps_.clear();
1338 if (z3disasm_selected_index_ < 0 ||
1339 z3disasm_selected_index_ >=
static_cast<int>(z3disasm_files_.size())) {
1343 z3disasm_selected_path_ = z3disasm_files_[z3disasm_selected_index_];
1344 std::ifstream file(z3disasm_selected_path_);
1345 if (!file.is_open()) {
1346 z3disasm_status_ = absl::StrCat(
"Failed to open ", z3disasm_selected_path_);
1349 z3disasm_selected_contents_.assign(std::istreambuf_iterator<char>(file),
1350 std::istreambuf_iterator<char>());
1351 RefreshSelectedZ3DisassemblyMetadata();
1354void AssemblyEditor::RefreshZ3DisassemblyFiles() {
1355 z3disasm_files_.clear();
1356 const std::filesystem::path output_dir(ResolveZ3DisasmOutputDir());
1358 if (!std::filesystem::exists(output_dir, ec) || ec) {
1359 z3disasm_selected_index_ = -1;
1360 z3disasm_selected_path_.clear();
1361 z3disasm_selected_contents_.clear();
1362 z3disasm_source_jumps_.clear();
1363 z3disasm_hook_jumps_.clear();
1367 std::string previous_selection = z3disasm_selected_path_;
1368 for (
const auto& entry :
1369 std::filesystem::directory_iterator(output_dir, ec)) {
1373 if (!entry.is_regular_file()) {
1376 if (IsGeneratedBankFile(entry.path())) {
1377 z3disasm_files_.push_back(entry.path().string());
1380 std::sort(z3disasm_files_.begin(), z3disasm_files_.end());
1382 if (z3disasm_files_.empty()) {
1383 z3disasm_selected_index_ = -1;
1384 z3disasm_selected_path_.clear();
1385 z3disasm_selected_contents_.clear();
1386 z3disasm_source_jumps_.clear();
1387 z3disasm_hook_jumps_.clear();
1391 z3disasm_selected_index_ = 0;
1392 if (!previous_selection.empty()) {
1393 const auto it = std::find(z3disasm_files_.begin(), z3disasm_files_.end(),
1394 previous_selection);
1395 if (it != z3disasm_files_.end()) {
1396 z3disasm_selected_index_ =
1397 static_cast<int>(std::distance(z3disasm_files_.begin(), it));
1400 LoadSelectedZ3DisassemblyFile();
1403void AssemblyEditor::PollZ3DisassemblyTask() {
1404 const auto snapshot = z3disasm_task_.GetSnapshot();
1405 if (!snapshot.started) {
1408 if (snapshot.running) {
1410 absl::StrCat(
"z3disasm running...\n", snapshot.output_tail);
1413 if (z3disasm_task_acknowledged_ || !snapshot.finished) {
1417 z3disasm_task_acknowledged_ =
true;
1418 if (snapshot.status.ok()) {
1419 RefreshZ3DisassemblyFiles();
1420 z3disasm_status_ = absl::StrCat(
1421 "z3disasm finished.",
1422 z3disasm_files_.empty()
1423 ? std::string(
" No bank files were generated.")
1424 : absl::StrFormat(
" Loaded %d bank file(s).",
1425 static_cast<int>(z3disasm_files_.size())));
1428 absl::StrCat(
"z3disasm failed: ", snapshot.status.message(),
"\n",
1429 snapshot.output_tail);
1431 z3disasm_task_.Wait().IgnoreError();
1434absl::Status AssemblyEditor::GenerateZ3Disassembly() {
1435 if (
const auto snapshot = z3disasm_task_.GetSnapshot(); snapshot.running) {
1436 return absl::FailedPreconditionError(
"z3disasm is already running");
1439 const std::string rom_path = ResolveZ3DisasmRomPath();
1440 if (rom_path.empty()) {
1441 return absl::FailedPreconditionError(
1442 "No ROM path is available for z3disasm");
1445 const std::string command_path = ResolveZ3DisasmCommand();
1446 const std::string output_dir = ResolveZ3DisasmOutputDir();
1447 z3disasm_output_dir_ = output_dir;
1450 std::filesystem::create_directories(output_dir, ec);
1451 for (
const auto& entry :
1452 std::filesystem::directory_iterator(output_dir, ec)) {
1453 if (!ec && entry.is_regular_file() && IsGeneratedBankFile(entry.path())) {
1454 std::filesystem::remove(entry.path(), ec);
1458 std::string command = ShellQuote(command_path);
1459 command +=
" --rom ";
1460 command += ShellQuote(rom_path);
1461 command +=
" --out ";
1462 command += ShellQuote(output_dir);
1464 if (!z3disasm_all_banks_) {
1465 command += absl::StrFormat(
" --bank-start %02X --bank-end %02X",
1466 std::clamp(z3disasm_bank_start_, 0, 0xFF),
1467 std::clamp(z3disasm_bank_end_, 0, 0xFF));
1470 if (dependencies_.project) {
1471 const std::string symbols_path =
1472 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb.empty()
1473 ? dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
1474 : dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
1475 if (!symbols_path.empty() && std::filesystem::exists(symbols_path)) {
1476 command +=
" --symbols ";
1477 command += ShellQuote(symbols_path);
1480 const std::string hooks_path =
1481 !dependencies_.project->z3dk_settings.artifact_paths.hooks_json.empty()
1482 ? dependencies_.project->z3dk_settings.artifact_paths.hooks_json
1483 : dependencies_.project->GetZ3dkArtifactPath(
"hooks.json");
1484 if (!hooks_path.empty() && std::filesystem::exists(hooks_path)) {
1485 command +=
" --hooks ";
1486 command += ShellQuote(hooks_path);
1490 z3disasm_task_acknowledged_ =
false;
1491 z3disasm_selected_index_ = -1;
1492 z3disasm_selected_path_.clear();
1493 z3disasm_selected_contents_.clear();
1494 z3disasm_source_jumps_.clear();
1495 z3disasm_hook_jumps_.clear();
1496 z3disasm_files_.clear();
1497 z3disasm_status_ =
"Launching z3disasm...";
1498 return z3disasm_task_.Start(command,
1499 std::filesystem::current_path().
string());
1502absl::Status AssemblyEditor::NavigateDisassemblyQuery() {
1503 auto symbol_it = symbols_.find(disasm_query_);
1504 if (symbol_it != symbols_.end()) {
1505 disasm_query_ = absl::StrCat(
"0x", absl::Hex(symbol_it->second.address));
1506 disasm_status_ = absl::StrCat(
"Resolved symbol ", symbol_it->first);
1507 return absl::OkStatus();
1510 auto parsed = ParseHexAddress(disasm_query_);
1511 if (!parsed.has_value()) {
1512 return absl::InvalidArgumentError(
"Enter a SNES address or known symbol");
1515 disasm_query_ = absl::StrCat(
"0x", absl::Hex(*parsed));
1516 disasm_status_ = absl::StrFormat(
"Showing disassembly at $%06X", *parsed);
1517 return absl::OkStatus();
1520void AssemblyEditor::DrawDisassemblyContent() {
1521 PollZ3DisassemblyTask();
1523 if (z3disasm_output_dir_.empty()) {
1524 z3disasm_output_dir_ = ResolveZ3DisasmOutputDir();
1526 if (z3disasm_files_.empty()) {
1527 RefreshZ3DisassemblyFiles();
1530 ImGui::TextDisabled(
1531 tr(
"z3disasm-backed bank browser for generated `bank_XX.asm` files."));
1532 const std::string rom_path = ResolveZ3DisasmRomPath();
1533 ImGui::TextWrapped(tr(
"ROM: %s"),
1534 rom_path.empty() ?
"<unavailable>" : rom_path.c_str());
1535 ImGui::TextWrapped(tr(
"Output: %s"), z3disasm_output_dir_.c_str());
1537 ImGui::Checkbox(tr(
"All banks"), &z3disasm_all_banks_);
1538 if (!z3disasm_all_banks_) {
1540 ImGui::SetNextItemWidth(70.0f);
1541 ImGui::InputInt(tr(
"Start"), &z3disasm_bank_start_);
1543 ImGui::SetNextItemWidth(70.0f);
1544 ImGui::InputInt(tr(
"End"), &z3disasm_bank_end_);
1545 z3disasm_bank_start_ = std::clamp(z3disasm_bank_start_, 0, 0xFF);
1546 z3disasm_bank_end_ = std::clamp(z3disasm_bank_end_, 0, 0xFF);
1547 if (z3disasm_bank_end_ < z3disasm_bank_start_) {
1548 z3disasm_bank_end_ = z3disasm_bank_start_;
1551 if (ImGui::SmallButton(tr(
"Use Query Bank"))) {
1552 if (
auto bank = CurrentDisassemblyBank(); bank.has_value()) {
1553 z3disasm_bank_start_ =
static_cast<int>(*bank);
1554 z3disasm_bank_end_ =
static_cast<int>(*bank);
1559 const auto task_snapshot = z3disasm_task_.GetSnapshot();
1560 const bool can_generate = !rom_path.empty();
1561 ImGui::BeginDisabled(!can_generate || task_snapshot.running);
1563 auto status = GenerateZ3Disassembly();
1565 z3disasm_status_ = std::string(status.message());
1566 if (dependencies_.toast_manager) {
1567 dependencies_.toast_manager->Show(z3disasm_status_, ToastType::kError);
1571 ImGui::EndDisabled();
1572 if (task_snapshot.running) {
1575 z3disasm_task_.Cancel();
1578 if (!can_generate) {
1580 ImGui::TextDisabled(tr(
"Load or configure a ROM path first."));
1583 if (!z3disasm_status_.empty()) {
1585 ImGui::TextWrapped(
"%s", z3disasm_status_.c_str());
1588 if (!task_snapshot.output_tail.empty()) {
1589 if (ImGui::CollapsingHeader(tr(
"z3disasm Output"))) {
1590 std::string output_tail = task_snapshot.output_tail;
1591 ImGui::InputTextMultiline(
"##z3disasm_output", &output_tail,
1592 ImVec2(-1.0f, 80.0f),
1593 ImGuiInputTextFlags_ReadOnly);
1597 ImGui::SeparatorText(tr(
"Bank Browser"));
1598 if (z3disasm_files_.empty()) {
1599 ImGui::TextDisabled(tr(
1600 "No generated bank files yet. Run z3disasm to populate this browser."));
1602 if (ImGui::BeginTable(
1603 "##z3disasm_browser", 2,
1604 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
1605 ImGui::TableSetupColumn(
"Banks", ImGuiTableColumnFlags_WidthFixed,
1607 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch);
1608 ImGui::TableNextRow();
1610 ImGui::TableSetColumnIndex(0);
1611 if (ImGui::BeginChild(
"##z3disasm_list", ImVec2(0, 0),
true)) {
1612 for (
int i = 0; i < static_cast<int>(z3disasm_files_.size()); ++i) {
1613 const std::string name =
1614 std::filesystem::path(z3disasm_files_[i]).filename().string();
1615 if (ImGui::Selectable(name.c_str(), z3disasm_selected_index_ == i)) {
1616 z3disasm_selected_index_ = i;
1617 LoadSelectedZ3DisassemblyFile();
1623 ImGui::TableSetColumnIndex(1);
1624 if (ImGui::BeginChild(
"##z3disasm_preview", ImVec2(0, 0),
true)) {
1625 if (!z3disasm_selected_path_.empty()) {
1626 const std::string selected_name =
1627 std::filesystem::path(z3disasm_selected_path_)
1630 ImGui::TextUnformatted(selected_name.c_str());
1632 if (ImGui::SmallButton(tr(
"Open in Code Editor"))) {
1633 ChangeActiveFile(z3disasm_selected_path_);
1635 const std::string bank_query = BuildProjectGraphBankQuery();
1636 if (!bank_query.empty()) {
1638 if (ImGui::SmallButton(tr(
"Open Bank Graph"))) {
1639 auto status = RunProjectGraphQueryInDrawer(
1641 absl::StrFormat(
"--bank=%02X", SelectedZ3DisasmBankIndex()),
1643 absl::StrFormat(
"Project Graph Bank $%02X",
1644 SelectedZ3DisasmBankIndex()));
1646 z3disasm_status_ = std::string(status.message());
1650 if (ImGui::SmallButton(tr(
"Copy Bank Query"))) {
1651 ImGui::SetClipboardText(bank_query.c_str());
1653 ImGui::TextDisabled(
"%s", bank_query.c_str());
1656 if (!z3disasm_source_jumps_.empty() &&
1657 ImGui::CollapsingHeader(tr(
"Source Map Jumps"),
1658 ImGuiTreeNodeFlags_DefaultOpen)) {
1659 for (
const auto& jump : z3disasm_source_jumps_) {
1660 ImGui::PushID(
static_cast<int>(jump.address) ^ jump.line);
1661 const std::string source_label =
1662 absl::StrFormat(
"Source##source_jump_%06X", jump.address);
1663 const std::string graph_label =
1664 absl::StrFormat(
"Graph##source_graph_%06X", jump.address);
1665 const std::string copy_label =
1666 absl::StrFormat(
"Copy##source_copy_%06X", jump.address);
1667 const std::string addr_label =
1668 absl::StrFormat(
"Addr##source_addr_%06X", jump.address);
1669 if (ImGui::SmallButton(source_label.c_str())) {
1670 JumpToReference(absl::StrCat(jump.file,
":", jump.line))
1674 if (ImGui::SmallButton(graph_label.c_str())) {
1675 auto status = RunProjectGraphQueryInDrawer(
1677 absl::StrFormat(
"--address=%06X", jump.address),
1679 absl::StrFormat(
"Project Graph Lookup $%06X",
1682 z3disasm_status_ = std::string(status.message());
1686 if (ImGui::SmallButton(copy_label.c_str())) {
1687 const std::string query =
1688 BuildProjectGraphLookupQuery(jump.address);
1689 ImGui::SetClipboardText(query.c_str());
1692 if (ImGui::SmallButton(addr_label.c_str())) {
1693 disasm_query_ = absl::StrFormat(
"0x%06X", jump.address);
1694 NavigateDisassemblyQuery().IgnoreError();
1697 ImGui::TextWrapped(
"$%06X %s:%d", jump.address,
1698 jump.file.c_str(), jump.line);
1702 if (!z3disasm_hook_jumps_.empty() &&
1703 ImGui::CollapsingHeader(tr(
"Hook Jumps"),
1704 ImGuiTreeNodeFlags_DefaultOpen)) {
1705 for (
const auto& hook : z3disasm_hook_jumps_) {
1706 ImGui::PushID(
static_cast<int>(hook.address) ^ hook.size ^
1708 const std::string source_label =
1709 absl::StrFormat(
"Source##hook_jump_%06X", hook.address);
1710 const std::string graph_label =
1711 absl::StrFormat(
"Graph##hook_graph_%06X", hook.address);
1712 const std::string copy_label =
1713 absl::StrFormat(
"Copy##hook_copy_%06X", hook.address);
1714 const std::string addr_label =
1715 absl::StrFormat(
"Addr##hook_addr_%06X", hook.address);
1716 if (!hook.source.empty() &&
1717 ImGui::SmallButton(source_label.c_str())) {
1718 JumpToReference(hook.source).IgnoreError();
1720 if (!hook.source.empty()) {
1723 if (ImGui::SmallButton(graph_label.c_str())) {
1724 auto status = RunProjectGraphQueryInDrawer(
1726 absl::StrFormat(
"--address=%06X", hook.address),
1728 absl::StrFormat(
"Project Graph Lookup $%06X",
1731 z3disasm_status_ = std::string(status.message());
1735 if (ImGui::SmallButton(copy_label.c_str())) {
1736 const std::string query =
1737 BuildProjectGraphLookupQuery(hook.address);
1738 ImGui::SetClipboardText(query.c_str());
1741 if (ImGui::SmallButton(addr_label.c_str())) {
1742 disasm_query_ = absl::StrFormat(
"0x%06X", hook.address);
1743 NavigateDisassemblyQuery().IgnoreError();
1746 ImGui::TextWrapped(
"$%06X %s %s (+%d) %s", hook.address,
1747 hook.kind.c_str(), hook.name.c_str(),
1748 hook.size, hook.source.c_str());
1753 ImGui::InputTextMultiline(
1754 "##z3disasm_text", &z3disasm_selected_contents_,
1755 ImVec2(-1.0f, -1.0f), ImGuiInputTextFlags_ReadOnly);
1757 ImGui::TextDisabled(
1758 tr(
"Select a generated bank file to preview it."));
1766 ImGui::SeparatorText(tr(
"Quick ROM Slice"));
1767 ImGui::TextDisabled(tr(
"Inline viewer for the currently loaded ROM buffer."));
1768 ImGui::SetNextItemWidth(220.0f);
1769 ImGui::InputText(tr(
"Address or Symbol"), &disasm_query_);
1772 auto status = NavigateDisassemblyQuery();
1774 disasm_status_ = std::string(status.message());
1778 ImGui::SetNextItemWidth(80.0f);
1779 ImGui::InputInt(tr(
"Count"), &disasm_instruction_count_);
1780 if (disasm_instruction_count_ < 1) {
1781 disasm_instruction_count_ = 1;
1783 if (disasm_instruction_count_ > 128) {
1784 disasm_instruction_count_ = 128;
1787 if (!disasm_status_.empty()) {
1788 ImGui::TextDisabled(
"%s", disasm_status_.c_str());
1792 if (!rom_ || !rom_->is_loaded()) {
1793 ImGui::TextDisabled(tr(
"Load a ROM to browse disassembly."));
1797 uint32_t start_address = 0;
1798 if (
auto symbol_it = symbols_.find(disasm_query_);
1799 symbol_it != symbols_.end()) {
1800 start_address = symbol_it->second.address;
1802 auto parsed = ParseHexAddress(disasm_query_);
1803 if (!parsed.has_value()) {
1804 ImGui::TextDisabled(
1805 tr(
"Enter a SNES address like 0x008000 or a known label."));
1808 start_address = *parsed;
1811 std::map<uint32_t, std::string> symbols_by_address;
1812 for (
const auto& [name, symbol] : symbols_) {
1813 symbols_by_address.emplace(symbol.address, name);
1818 auto it = symbols_by_address.find(address);
1819 return it == symbols_by_address.end() ? std::string() : it->second;
1822 auto read_byte = [
this](uint32_t snes_addr) -> uint8_t {
1823 if (!rom_ || !rom_->is_loaded()) {
1826 const uint32_t pc_addr =
SnesToPc(snes_addr);
1827 if (pc_addr >= rom_->size()) {
1830 return rom_->vector()[pc_addr];
1834 start_address,
static_cast<size_t>(disasm_instruction_count_), read_byte);
1835 if (instructions.empty()) {
1836 ImGui::TextDisabled(tr(
"No disassembly available for this address."));
1840 if (ImGui::BeginTable(
"##assembly_disasm", 3,
1841 ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
1842 ImGuiTableFlags_Resizable)) {
1843 ImGui::TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed,
1845 ImGui::TableSetupColumn(
"Instruction", ImGuiTableColumnFlags_WidthStretch);
1846 ImGui::TableSetupColumn(
"Context", ImGuiTableColumnFlags_WidthFixed,
1848 ImGui::TableHeadersRow();
1850 for (
const auto& instruction : instructions) {
1851 ImGui::PushID(
static_cast<int>(instruction.address));
1852 ImGui::TableNextRow();
1854 ImGui::TableSetColumnIndex(0);
1855 const std::string address_label =
1856 absl::StrFormat(
"$%06X", instruction.address);
1857 ImGui::TextUnformatted(address_label.c_str());
1859 ImGui::TableSetColumnIndex(1);
1860 if (
auto label_it = symbols_by_address.find(instruction.address);
1861 label_it != symbols_by_address.end()) {
1863 "%s:", label_it->second.c_str());
1865 ImGui::TextWrapped(
"%s", instruction.full_text.c_str());
1867 ImGui::TableSetColumnIndex(2);
1868 if (instruction.branch_target != 0) {
1869 auto target_it = symbols_by_address.find(instruction.branch_target);
1870 if (target_it != symbols_by_address.end()) {
1871 if (ImGui::SmallButton(target_it->second.c_str())) {
1872 JumpToSymbolDefinition(target_it->second).IgnoreError();
1875 ImGui::Text(
"$%06X", instruction.branch_target);
1878 ImGui::TextDisabled(
"-");
1886void AssemblyEditor::DrawToolbarContent() {
1887 float button_size = 32.0f;
1890 ImVec2(button_size, button_size))) {
1891 auto folder = FileDialogWrapper::ShowOpenFolderDialog();
1892 if (!folder.empty()) {
1893 current_folder_ = LoadFolder(folder);
1899 ImVec2(button_size, button_size))) {
1900 auto filename = FileDialogWrapper::ShowOpenFileDialog();
1901 if (!filename.empty()) {
1902 ChangeActiveFile(filename);
1907 bool can_save = HasActiveFile();
1908 ImGui::BeginDisabled(!can_save);
1910 ImVec2(button_size, button_size))) {
1913 ImGui::EndDisabled();
1920 ImGui::BeginDisabled(!can_save);
1922 ImVec2(button_size, button_size))) {
1923 ValidateCurrentFile();
1925 ImGui::EndDisabled();
1928 bool can_apply = can_save && rom_ && rom_->is_loaded();
1929 ImGui::BeginDisabled(!can_apply);
1931 ImVec2(button_size, button_size))) {
1934 ImGui::EndDisabled();
1937void AssemblyEditor::DrawFileTabView() {
1938 if (active_files_.empty()) {
1943 ImGuiTabBarFlags_Reorderable |
1944 ImGuiTabBarFlags_AutoSelectNewTabs |
1945 ImGuiTabBarFlags_FittingPolicyScroll)) {
1946 for (
int i = 0; i < active_files_.Size; i++) {
1947 int file_id = active_files_[i];
1948 if (file_id >= files_.size()) {
1953 std::string filename = files_[file_id];
1954 size_t pos = filename.find_last_of(
"/\\");
1955 if (pos != std::string::npos) {
1956 filename = filename.substr(pos + 1);
1959 bool is_active = (active_file_id_ == file_id);
1960 ImGuiTabItemFlags flags = is_active ? ImGuiTabItemFlags_SetSelected : 0;
1961 bool tab_open =
true;
1963 if (ImGui::BeginTabItem(filename.c_str(), &tab_open, flags)) {
1966 active_file_id_ = file_id;
1969 ImGui::EndTabItem();
1974 active_files_.erase(active_files_.Data + i);
1975 if (active_file_id_ == file_id) {
1976 active_file_id_ = active_files_.empty() ? -1 : active_files_[0];
1977 if (active_file_id_ >= 0 &&
1978 active_file_id_ <
static_cast<int>(open_files_.size())) {
1981 current_file_.clear();
1995absl::Status AssemblyEditor::Update() {
1997 return absl::OkStatus();
2000 if (dependencies_.window_manager !=
nullptr) {
2001 return absl::OkStatus();
2006 ImGui::Begin(
"Assembly Editor", &active_, ImGuiWindowFlags_MenuBar);
2013 return absl::OkStatus();
2016void AssemblyEditor::InlineUpdate() {
2019 const char* file_label =
2020 current_file_.empty() ?
"No file" : current_file_.c_str();
2021 ImGui::Text(tr(
"%6d/%-6d %6d lines | %s | %s | %s | %s"), cpos.mLine + 1,
2024 editor->
CanUndo() ?
"*" :
" ",
2027 editor->
Render(
"##asm_editor", ImVec2(0, 0));
2030void AssemblyEditor::UpdateCodeView() {
2033 DrawToolbarContent();
2038absl::Status AssemblyEditor::Save() {
2039 if (!HasActiveFile()) {
2040 return absl::FailedPreconditionError(
"No active file to save.");
2043 const std::string& path = files_[active_file_id_];
2044 std::ofstream file(path);
2045 if (!file.is_open()) {
2046 return absl::InvalidArgumentError(
2047 absl::StrCat(
"Cannot write file: ", path));
2050 file << GetActiveEditor()->GetText();
2052 return absl::OkStatus();
2055void AssemblyEditor::DrawToolset() {
2060 current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
2069void AssemblyEditor::DrawCurrentFolder() {
2071 if (current_folder_.name.empty() && dependencies_.project &&
2072 !dependencies_.project->code_folder.empty()) {
2073 OpenFolder(dependencies_.project->GetAbsolutePath(
2074 dependencies_.project->code_folder));
2077 if (ImGui::BeginChild(
"##current_folder", ImVec2(0, 0),
true,
2078 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
2079 if (ImGui::BeginTable(
"##file_table", 2,
2080 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
2081 ImGuiTableFlags_Resizable |
2082 ImGuiTableFlags_Sortable)) {
2083 ImGui::TableSetupColumn(
"Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
2084 ImGui::TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthStretch);
2086 ImGui::TableHeadersRow();
2088 for (
const auto& file : current_folder_.files) {
2089 ImGui::TableNextRow();
2090 ImGui::TableNextColumn();
2091 if (ImGui::Selectable(file.c_str())) {
2092 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/", file));
2094 ImGui::TableNextColumn();
2095 ImGui::Text(tr(
"File"));
2098 for (
const auto& subfolder : current_folder_.subfolders) {
2099 ImGui::TableNextRow();
2100 ImGui::TableNextColumn();
2101 if (ImGui::TreeNode(subfolder.name.c_str())) {
2102 for (
const auto& file : subfolder.files) {
2103 ImGui::TableNextRow();
2104 ImGui::TableNextColumn();
2105 if (ImGui::Selectable(file.c_str())) {
2106 ChangeActiveFile(absl::StrCat(current_folder_.name,
"/",
2107 subfolder.name,
"/", file));
2109 ImGui::TableNextColumn();
2110 ImGui::Text(tr(
"File"));
2114 ImGui::TableNextColumn();
2115 ImGui::Text(tr(
"Folder"));
2125void AssemblyEditor::DrawFileMenu() {
2126 if (ImGui::BeginMenu(tr(
"File"))) {
2129 if (!filename.empty()) {
2130 ChangeActiveFile(filename);
2140void AssemblyEditor::DrawEditMenu() {
2141 if (ImGui::BeginMenu(tr(
"Edit"))) {
2143 GetActiveEditor()->Undo();
2146 GetActiveEditor()->Redo();
2150 GetActiveEditor()->Cut();
2153 GetActiveEditor()->Copy();
2156 GetActiveEditor()->Paste();
2166void AssemblyEditor::ChangeActiveFile(
const std::string_view& filename) {
2167 if (filename.empty()) {
2172 for (
int i = 0; i < active_files_.Size; ++i) {
2173 int file_id = active_files_[i];
2174 if (files_[file_id] == filename) {
2176 active_file_id_ = file_id;
2185 int new_file_id = files_.size();
2186 files_.push_back(std::string(filename));
2187 active_files_.push_back(new_file_id);
2190 if (new_file_id >= open_files_.size()) {
2191 open_files_.resize(new_file_id + 1);
2194 open_files_[new_file_id].SetText(content);
2195 open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef());
2197 open_files_[new_file_id].SetShowWhitespaces(
false);
2198 active_file_id_ = new_file_id;
2200 }
catch (
const std::exception& ex) {
2201 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error opening file: %s\n",
2206absl::Status AssemblyEditor::Cut() {
2207 GetActiveEditor()->Cut();
2208 return absl::OkStatus();
2211absl::Status AssemblyEditor::Copy() {
2212 GetActiveEditor()->Copy();
2213 return absl::OkStatus();
2216absl::Status AssemblyEditor::Paste() {
2217 GetActiveEditor()->Paste();
2218 return absl::OkStatus();
2221bool AssemblyEditor::CanUndo()
const {
2222 const TextEditor* editor = GetActiveEditor();
2223 return editor !=
nullptr && editor->
CanUndo();
2226bool AssemblyEditor::CanRedo()
const {
2227 const TextEditor* editor = GetActiveEditor();
2228 return editor !=
nullptr && editor->
CanRedo();
2231absl::Status AssemblyEditor::Undo() {
2232 GetActiveEditor()->Undo();
2233 return absl::OkStatus();
2236absl::Status AssemblyEditor::Redo() {
2237 GetActiveEditor()->Redo();
2238 return absl::OkStatus();
2241#ifdef YAZE_WITH_Z3DK
2244 if (!dependencies_.project) {
2248 const auto&
z3dk = dependencies_.project->z3dk_settings;
2263 for (
const auto& range :
z3dk.prohibited_memory_ranges) {
2265 {.start = range.start, .end = range.end, .reason = range.reason});
2268 auto append_unique = [&options](
const std::string& path) {
2278 append_unique(dependencies_.project->code_folder);
2279 if (HasActiveFile()) {
2281 std::filesystem::path(files_[active_file_id_]).parent_path().
string());
2284 if (rom_ && rom_->is_loaded() && !rom_->filename().empty()) {
2286 }
else if (!
z3dk.rom_path.empty()) {
2293void AssemblyEditor::ExportZ3dkArtifacts(
const core::AsarPatchResult& result,
2294 bool sync_mesen_symbols) {
2295 if (!dependencies_.project) {
2299 const auto& project = *dependencies_.project;
2300 const auto& configured = project.z3dk_settings.artifact_paths;
2301 const std::string symbols_path =
2302 configured.symbols_mlb.empty()
2303 ? project.GetZ3dkArtifactPath(
"symbols.mlb")
2304 : configured.symbols_mlb;
2305 const std::string sourcemap_path =
2306 configured.sourcemap_json.empty()
2307 ? project.GetZ3dkArtifactPath(
"sourcemap.json")
2308 : configured.sourcemap_json;
2309 const std::string annotations_path =
2310 configured.annotations_json.empty()
2311 ? project.GetZ3dkArtifactPath(
"annotations.json")
2312 : configured.annotations_json;
2313 const std::string hooks_path = configured.hooks_json.empty()
2314 ? project.GetZ3dkArtifactPath(
"hooks.json")
2315 : configured.hooks_json;
2316 const std::string lint_path = configured.lint_json.empty()
2317 ? project.GetZ3dkArtifactPath(
"lint.json")
2318 : configured.lint_json;
2319 auto write_artifact = [](
const std::string& path,
2320 const std::string& content) {
2321 if (path.empty() || content.empty()) {
2325 std::filesystem::create_directories(
2326 std::filesystem::path(path).parent_path(), ec);
2327 std::ofstream file(path, std::ios::binary | std::ios::trunc);
2328 if (!file.is_open()) {
2334 write_artifact(symbols_path, result.symbols_mlb);
2335 write_artifact(sourcemap_path, result.sourcemap_json);
2336 write_artifact(annotations_path, result.annotations_json);
2337 write_artifact(hooks_path, result.hooks_json);
2338 write_artifact(lint_path, result.lint_json);
2340 if (!sync_mesen_symbols || symbols_path.empty() ||
2341 result.symbols_mlb.empty()) {
2346 if (!client->IsConnected()) {
2347 client->Connect().IgnoreError();
2349 if (!client->IsConnected()) {
2353 auto status = client->LoadSymbolsFile(symbols_path);
2355 if (dependencies_.toast_manager) {
2356 dependencies_.toast_manager->Show(
2357 "Failed to sync Mesen2 symbols: " + std::string(status.message()),
2358 ToastType::kWarning);
2360 }
else if (dependencies_.toast_manager) {
2361 dependencies_.toast_manager->Show(
2362 "Synced symbols to Mesen2 from " + symbols_path, ToastType::kSuccess);
2371absl::Status AssemblyEditor::ValidateCurrentFile() {
2372 if (!HasActiveFile()) {
2373 return absl::FailedPreconditionError(
"No file is currently active");
2376 const std::string& file_path = files_[active_file_id_];
2378#ifdef YAZE_WITH_Z3DK
2379 const auto options = BuildZ3dkAssembleOptions();
2383 std::vector<uint8_t> scratch;
2384 auto result_or = z3dk_.ApplyPatch(file_path, scratch, options);
2385 if (!result_or.ok()) {
2386 last_errors_.clear();
2387 last_errors_.push_back(std::string(result_or.status().message()));
2388 last_warnings_.clear();
2389 last_diagnostics_.clear();
2391 GetActiveEditor()->SetErrorMarkers(empty_markers);
2392 return result_or.status();
2394 UpdateErrorMarkers(*result_or);
2395 if (!result_or->success) {
2396 return absl::InternalError(
"Assembly validation failed");
2398 ExportZ3dkArtifacts(*result_or,
false);
2399 ClearErrorMarkers();
2400 return absl::OkStatus();
2403 if (!asar_initialized_) {
2404 auto status = asar_.Initialize();
2408 asar_initialized_ =
true;
2412 auto status = asar_.ValidateAssembly(file_path);
2417 last_errors_.clear();
2418 last_errors_.push_back(std::string(status.message()));
2422 for (
const auto& error : last_errors_) {
2424 size_t line_pos = error.find(
':');
2425 if (line_pos != std::string::npos) {
2426 size_t num_start = line_pos + 1;
2427 size_t num_end = error.find(
':', num_start);
2428 if (num_end != std::string::npos) {
2429 std::string line_str = error.substr(num_start, num_end - num_start);
2431 int line = std::stoi(line_str);
2432 markers[line] = error;
2439 GetActiveEditor()->SetErrorMarkers(markers);
2444 ClearErrorMarkers();
2445 return absl::OkStatus();
2449absl::Status AssemblyEditor::ApplyPatchToRom() {
2450 if (!rom_ || !rom_->is_loaded()) {
2451 return absl::FailedPreconditionError(
"No ROM is loaded");
2454 if (!HasActiveFile()) {
2455 return absl::FailedPreconditionError(
"No file is currently active");
2458 const std::string& file_path = files_[active_file_id_];
2459 std::vector<uint8_t> rom_data = rom_->vector();
2461#ifdef YAZE_WITH_Z3DK
2462 const auto options = BuildZ3dkAssembleOptions();
2463 auto result = z3dk_.ApplyPatch(file_path, rom_data, options);
2465 last_errors_.clear();
2466 last_errors_.push_back(std::string(result.status().message()));
2467 last_warnings_.clear();
2468 last_diagnostics_.clear();
2470 GetActiveEditor()->SetErrorMarkers(empty_markers);
2471 return result.status();
2473 UpdateErrorMarkers(*result);
2474 if (!result->success) {
2475 return absl::InternalError(
"Patch application failed");
2477 rom_->LoadFromData(rom_data);
2478 symbols_ = z3dk_.GetSymbolTable();
2479 ExportZ3dkArtifacts(*result,
true);
2480 ClearErrorMarkers();
2481 return absl::OkStatus();
2484 if (!asar_initialized_) {
2485 auto status = asar_.Initialize();
2489 asar_initialized_ =
true;
2493 auto result = asar_.ApplyPatch(file_path, rom_data);
2496 UpdateErrorMarkers(*result);
2497 return result.status();
2500 if (result->success) {
2502 rom_->LoadFromData(rom_data);
2505 symbols_ = asar_.GetSymbolTable();
2506 last_errors_.clear();
2507 last_warnings_ = result->warnings;
2510 ClearErrorMarkers();
2512 return absl::OkStatus();
2514 UpdateErrorMarkers(*result);
2515 return absl::InternalError(
"Patch application failed");
2521 last_errors_ = result.
errors;
2525 if (!HasActiveFile()) {
2539 markers[d.line] = d.message;
2544 for (
const auto& error : result.
errors) {
2546 size_t first_colon = error.find(
':');
2547 if (first_colon != std::string::npos) {
2548 size_t second_colon = error.find(
':', first_colon + 1);
2549 if (second_colon != std::string::npos) {
2550 std::string line_str =
2551 error.substr(first_colon + 1, second_colon - (first_colon + 1));
2552 int line = std::stoi(line_str);
2553 markers[line] = error;
2562 GetActiveEditor()->SetErrorMarkers(markers);
2565void AssemblyEditor::ClearErrorMarkers() {
2566 last_errors_.clear();
2567 last_diagnostics_.clear();
2569 if (!HasActiveFile()) {
2574 GetActiveEditor()->SetErrorMarkers(empty_markers);
2577void AssemblyEditor::DrawAssembleMenu() {
2578 if (ImGui::BeginMenu(tr(
"Assemble"))) {
2579 bool has_active_file = HasActiveFile();
2580 bool has_rom = (rom_ && rom_->is_loaded());
2584 auto status = ValidateCurrentFile();
2590 if (ImGui::MenuItem(
ICON_MD_BUILD " Apply to ROM",
"Ctrl+Shift+B",
false,
2591 has_active_file && has_rom)) {
2592 auto status = ApplyPatchToRom();
2600 if (dependencies_.project) {
2601 std::string sym_file = dependencies_.project->symbols_filename;
2602 if (dependencies_.project->HasZ3dkConfig() &&
2603 !dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb
2606 dependencies_.project->z3dk_settings.artifact_paths.symbols_mlb;
2607 }
else if (sym_file.empty() || !std::filesystem::exists(sym_file)) {
2608 sym_file = dependencies_.project->GetZ3dkArtifactPath(
"symbols.mlb");
2610 if (!sym_file.empty()) {
2611 std::string abs_path = sym_file;
2612 if (!std::filesystem::path(abs_path).is_absolute()) {
2613 abs_path = dependencies_.project->GetAbsolutePath(sym_file);
2615 auto status = asar_.LoadSymbolsFromFile(abs_path);
2618 symbols_ = asar_.GetSymbolTable();
2619 if (dependencies_.toast_manager) {
2620 dependencies_.toast_manager->Show(
2621 "Successfully loaded external symbols from " + sym_file,
2622 ToastType::kSuccess);
2625 if (dependencies_.toast_manager) {
2626 dependencies_.toast_manager->Show(
2627 "Failed to load symbols: " + std::string(status.message()),
2632 if (dependencies_.toast_manager) {
2633 dependencies_.toast_manager->Show(
2634 "Project does not specify a symbols file.",
2635 ToastType::kWarning);
2643 if (ImGui::MenuItem(
ICON_MD_LIST " Show Symbols",
nullptr,
2644 show_symbol_panel_)) {
2645 show_symbol_panel_ = !show_symbol_panel_;
2651 ImGui::TextDisabled(tr(
"Errors: %zu, Warnings: %zu"), last_errors_.size(),
2652 last_warnings_.size());
2657 if (ImGui::BeginMenu(tr(
"Version"))) {
2658 bool has_version_manager = (dependencies_.version_manager !=
nullptr);
2660 has_version_manager)) {
2661 if (has_version_manager) {
2662 ImGui::OpenPopup(
"Create Snapshot");
2667 if (ImGui::BeginPopupModal(
"Create Snapshot",
nullptr,
2668 ImGuiWindowFlags_AlwaysAutoResize)) {
2669 static char message[256] =
"";
2670 ImGui::InputText(tr(
"Message"), message,
sizeof(message));
2673 auto result = dependencies_.version_manager->CreateSnapshot(message);
2674 if (result.ok() && result->success) {
2675 if (dependencies_.toast_manager) {
2676 dependencies_.toast_manager->Show(
2677 "Snapshot Created: " + result->commit_hash,
2678 ToastType::kSuccess);
2681 if (dependencies_.toast_manager) {
2682 std::string err = result.ok()
2684 : std::string(result.status().message());
2685 dependencies_.toast_manager->Show(
"Snapshot Failed: " + err,
2689 ImGui::CloseCurrentPopup();
2694 ImGui::CloseCurrentPopup();
2703void AssemblyEditor::DrawSymbolPanel() {
2704 if (!show_symbol_panel_) {
2708 ImGui::SetNextWindowSize(ImVec2(350, 400), ImGuiCond_FirstUseEver);
2709 if (ImGui::Begin(
"Symbols", &show_symbol_panel_)) {
2710 if (symbols_.empty()) {
2711 ImGui::TextDisabled(tr(
"No symbols loaded."));
2712 ImGui::TextDisabled(tr(
"Apply a patch to load symbols."));
2715 static char filter[256] =
"";
2716 ImGui::InputTextWithHint(
"##symbol_filter",
"Filter symbols...", filter,
2721 if (ImGui::BeginChild(
"##symbol_list", ImVec2(0, 0),
true)) {
2722 for (
const auto& [name, symbol] : symbols_) {
2724 if (filter[0] !=
'\0' && name.find(filter) == std::string::npos) {
2728 ImGui::PushID(name.c_str());
2729 if (ImGui::Selectable(name.c_str())) {
2733 ImGui::SameLine(200);
2734 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, std::string action_label={}, std::function< void()> on_action=nullptr)
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 ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled, const char *panel_id, const char *anim_id)
Draw a standard icon button with theme-aware colors.
bool PrimaryButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a primary action button (accented color).
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
bool ThemedButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a standard text button with theme colors.
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