13#include "nlohmann/json.hpp"
14#include "absl/strings/match.h"
15#include "absl/strings/str_format.h"
16#include "absl/strings/str_split.h"
17#include "absl/strings/strip.h"
27 std::ifstream file(path);
28 if (!file.is_open()) {
29 return absl::NotFoundError(
30 absl::StrFormat(
"Failed to open file: %s", path));
32 std::stringstream buffer;
33 buffer << file.rdbuf();
39 std::string clean = str;
41 if (!clean.empty() && clean[0] ==
'$') {
42 clean = clean.substr(1);
45 if (clean.size() >= 2 && clean[0] ==
'0' &&
46 (clean[1] ==
'x' || clean[1] ==
'X')) {
47 clean = clean.substr(2);
50 if (!clean.empty() && clean.back() ==
':') {
54 if (clean.empty() || clean.size() > 6)
59 uint32_t addr = std::stoul(clean, &pos, 16);
60 if (pos != clean.size())
74 if (!std::isalpha(first) && first !=
'_' && first !=
'.')
77 for (
size_t i = 1; i < name.size(); ++i) {
79 if (!std::isalnum(c) && c !=
'_' && c !=
'.')
88 size_t starPos = std::string::npos;
91 while (s < str.size()) {
92 if (p < pattern.size() && (pattern[p] == str[s] || pattern[p] ==
'?')) {
95 }
else if (p < pattern.size() && pattern[p] ==
'*') {
98 }
else if (starPos != std::string::npos) {
106 while (p < pattern.size() && pattern[p] ==
'*')
108 return p == pattern.size();
113 size_t pos = path.find_last_of(
"/\\");
114 if (pos == std::string::npos)
116 return path.substr(pos + 1);
121 size_t pos = filename.find_last_of(
'.');
122 if (pos == std::string::npos)
124 return filename.substr(pos);
130 auto content_or = ReadFileContent(path);
131 if (!content_or.ok()) {
132 return content_or.status();
139 const std::string& directory_path) {
143 (void)directory_path;
144 return absl::UnimplementedError(
145 "Directory loading not supported in browser builds. "
146 "Please load individual symbol files.");
148 std::filesystem::path dir(directory_path);
149 if (!std::filesystem::exists(dir)) {
150 return absl::NotFoundError(
151 absl::StrFormat(
"Directory not found: %s", directory_path));
154 int files_loaded = 0;
155 for (
const auto& entry : std::filesystem::directory_iterator(dir)) {
156 if (entry.is_regular_file()) {
157 auto ext = entry.path().extension().string();
158 if (ext ==
".asm" || ext ==
".s") {
167 if (files_loaded == 0) {
168 return absl::NotFoundError(
"No ASM files found in directory");
171 return absl::OkStatus();
177 auto content_or = ReadFileContent(path);
178 if (!content_or.ok()) {
179 return content_or.status();
182 const std::string& content = *content_or;
183 std::string ext = GetExtension(path);
203 return absl::InvalidArgumentError(
"Unknown symbol format");
213 for (
const auto& sym : symbols) {
226 return it->second.name;
240 uint32_t address)
const {
241 std::vector<Symbol> result;
243 for (
auto it = range.first; it != range.second; ++it) {
244 result.push_back(it->second);
250 const std::string& name)
const {
259 const std::string& pattern)
const {
260 std::vector<Symbol> result;
262 if (WildcardMatch(pattern, name)) {
263 result.push_back(sym);
270 uint32_t end)
const {
271 std::vector<Symbol> result;
274 for (
auto it = it_start; it != it_end; ++it) {
275 result.push_back(it->second);
298 uint32_t max_offset)
const {
308 uint32_t offset = address - nearest->address;
309 if (offset <= max_offset) {
310 return absl::StrFormat(
"%s+$%X", nearest->name, offset);
315 return absl::StrFormat(
"$%06X", address);
320 if (exact && !exact->file.empty()) {
321 return absl::StrFormat(
"%s:%d", exact->file, exact->line);
325 if (nearest && !nearest->file.empty()) {
327 return absl::StrFormat(
"%s:%d", nearest->file, nearest->line);
334 return [
this](uint32_t address) -> std::string {
341 std::stringstream ss;
344 std::vector<Symbol> sorted_symbols;
347 sorted_symbols.push_back(pair.second);
350 std::sort(sorted_symbols.begin(), sorted_symbols.end(),
352 if (a.address != b.address) return a.address < b.address;
353 return a.name < b.name;
363 for (
const auto& sym : sorted_symbols) {
364 ss << absl::StrFormat(
"PRG:%X:%s\n", sym.address, sym.name);
370 for (
const auto& sym : sorted_symbols) {
371 uint32_t bank = (sym.address >> 16) & 0xFF;
372 uint32_t offset = sym.address & 0xFFFF;
373 ss << absl::StrFormat(
"%02X:%04X %s\n", bank, offset, sym.name);
381 for (
const auto& sym : sorted_symbols) {
382 ss << absl::StrFormat(
"org $%06X\n%s:\n", sym.address, sym.name);
388 for (
const auto& sym : sorted_symbols) {
389 ss << absl::StrFormat(
"%06X %s\n", sym.address, sym.name);
394 return absl::InvalidArgumentError(
"Unsupported export format");
401 const std::string& filename) {
402 std::istringstream stream(content);
406 std::string current_label;
407 uint32_t last_address = 0;
411 std::regex label_regex(R
"(^([A-Za-z_][A-Za-z0-9_]*):)");
413 std::regex local_label_regex(R
"(^(\.[A-Za-z_][A-Za-z0-9_]*))");
415 std::regex address_regex(R
"(^#_([0-9A-Fa-f]{6}):)");
417 bool pending_label =
false;
418 std::string pending_label_name;
419 bool pending_is_local =
false;
421 while (std::getline(stream, line)) {
425 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
426 if (trimmed.empty() || trimmed[0] ==
';')
432 if (std::regex_search(line, match, address_regex)) {
433 auto addr = ParseAddress(match[1].str());
435 last_address = *addr;
440 sym.
name = pending_label_name;
443 sym.
line = line_number;
447 pending_label =
false;
453 if (line[0] !=
' ' && line[0] !=
'\t' && line[0] !=
'#') {
454 if (std::regex_search(line, match, label_regex)) {
455 current_label = match[1].str();
456 pending_label =
true;
457 pending_label_name = current_label;
458 pending_is_local =
false;
463 if (std::regex_search(trimmed, match, local_label_regex)) {
464 std::string local_name = match[1].str();
466 std::string full_name =
467 current_label.empty() ? local_name : current_label + local_name;
468 pending_label =
true;
469 pending_label_name = full_name;
470 pending_is_local =
true;
474 return absl::OkStatus();
483 std::istringstream stream(content);
485 bool in_labels_section =
false;
489 std::regex label_regex(R
"(^([0-9A-Fa-f]{2}):([0-9A-Fa-f]{4})\s+(\S+))");
491 while (std::getline(stream, line)) {
492 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
494 if (trimmed ==
"[labels]") {
495 in_labels_section =
true;
498 if (trimmed.empty() || (trimmed[0] ==
'[' && trimmed !=
"[labels]")) {
499 if (trimmed[0] ==
'[')
500 in_labels_section =
false;
504 if (!in_labels_section)
508 if (std::regex_search(trimmed, match, label_regex)) {
509 uint32_t bank = std::stoul(match[1].str(),
nullptr, 16);
510 uint32_t offset = std::stoul(match[2].str(),
nullptr, 16);
511 uint32_t address = (bank << 16) | offset;
512 std::string name = match[3].str();
515 if (!name.empty() && name[0] ==
':') {
516 name = name.substr(1);
520 Symbol sym(name, address);
526 return absl::OkStatus();
535 std::istringstream stream(content);
538 while (std::getline(stream, line)) {
539 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
540 if (trimmed.empty() || trimmed[0] ==
';')
543 std::vector<std::string> parts = absl::StrSplit(trimmed,
':');
544 if (parts.size() < 2)
548 std::string addr_str;
549 std::string name_str;
551 auto first_addr = ParseAddress(parts[0]);
555 name_str = (parts.size() > 2 && ParseAddress(parts[1])) ? parts[2] : parts[1];
558 if (parts.size() < 3)
561 name_str = (parts.size() > 3 && ParseAddress(parts[2])) ? parts[3] : parts[2];
564 auto addr = ParseAddress(addr_str);
565 if (addr && !name_str.empty()) {
567 size_t marker_pos = name_str.find(
'@');
568 if (marker_pos != std::string::npos) {
569 name_str = name_str.substr(0, marker_pos);
570 name_str = std::string(absl::StripAsciiWhitespace(name_str));
573 if (!name_str.empty()) {
574 Symbol sym(name_str, *addr);
580 return absl::OkStatus();
588 std::istringstream stream(content);
591 std::regex label_regex(R
"(^([0-9A-Fa-f]{6})\s+(\S+))");
593 while (std::getline(stream, line)) {
594 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
595 if (trimmed.empty() || trimmed[0] ==
';' || trimmed[0] ==
'#')
599 if (std::regex_search(trimmed, match, label_regex)) {
600 auto addr = ParseAddress(match[1].str());
602 Symbol sym(match[2].str(), *addr);
608 return absl::OkStatus();
613 auto j = nlohmann::json::parse(content);
614 if (!j.contains(
"entries") || !j[
"entries"].is_array()) {
615 return absl::InvalidArgumentError(
"Invalid source map: missing entries");
619 std::map<int, std::string> file_map;
620 if (j.contains(
"files") && j[
"files"].is_array()) {
621 for (
const auto& f : j[
"files"]) {
622 int id = f.value(
"id", -1);
623 std::string path = f.value(
"path",
"");
624 if (
id != -1 && !path.empty()) {
631 for (
const auto& entry : j[
"entries"]) {
632 std::string addr_str = entry.value(
"address",
"");
633 auto addr_opt = ParseAddress(addr_str);
634 if (!addr_opt)
continue;
636 int file_id = entry.value(
"file_id", -1);
637 int line = entry.value(
"line", 0);
638 std::string symbol_name = entry.value(
"symbol",
"");
643 if (file_map.count(file_id)) {
644 sym.
file = file_map[file_id];
647 if (!symbol_name.empty()) {
648 sym.
name = symbol_name;
657 sym.
name = absl::StrFormat(
"src_%06X", sym.
address);
661 }
catch (
const std::exception& e) {
662 return absl::InvalidArgumentError(
663 absl::StrFormat(
"Failed to parse source map JSON: %s", e.what()));
666 return absl::OkStatus();
670 const std::string& extension)
const {
672 if (extension ==
".asm" || extension ==
".s") {
675 if (extension ==
".mlb") {
678 if (extension ==
".json") {
683 if (content.find(
"[labels]") != std::string::npos) {
686 if (content.find(
"PRG:") != std::string::npos) {
689 if (content.find(
"#_") != std::string::npos) {
std::function< std::string(uint32_t)> CreateResolver() const
Create a symbol resolver function for the disassembler.
std::vector< Symbol > GetSymbolsInRange(uint32_t start, uint32_t end) const
Get all symbols in an address range.
std::string FormatAddress(uint32_t address, uint32_t max_offset=0x100) const
Format an address with symbol info.
void AddSymbol(const Symbol &symbol)
Add a single symbol manually.
std::map< std::string, Symbol > symbols_by_name_
std::vector< Symbol > GetSymbolsAtAddress(uint32_t address) const
Get all symbols at an address (there may be multiple)
void AddAsarSymbols(const std::vector< Symbol > &symbols)
Add symbols from Asar patch results.
std::string GetSourceLocation(uint32_t address) const
Get source file and line for an address (for VS Code integration)
absl::Status ParseBsnesSymFile(const std::string &content)
absl::Status LoadAsarAsmDirectory(const std::string &directory_path)
Load symbols from a directory of ASM files.
absl::Status ParseWlaDxSymFile(const std::string &content)
std::multimap< uint32_t, Symbol > symbols_by_address_
absl::StatusOr< std::string > ExportSymbols(SymbolFormat format) const
Export all symbols to a string in the specified format.
absl::Status ParseSourceMapJson(const std::string &content)
absl::Status LoadAsarAsmFile(const std::string &path)
Load symbols from an Asar-style ASM file (usdasm format)
SymbolFormat DetectFormat(const std::string &content, const std::string &extension) const
absl::Status LoadSymbolFile(const std::string &path, SymbolFormat format=SymbolFormat::kAuto)
Load symbols from a .sym file (various formats)
std::string GetSymbolName(uint32_t address) const
Get symbol name for an address.
void Clear()
Clear all loaded symbols.
std::optional< Symbol > GetNearestSymbol(uint32_t address) const
Get nearest symbol at or before an address.
absl::Status ParseAsarAsmContent(const std::string &content, const std::string &filename)
absl::Status ParseMesenMlbFile(const std::string &content)
std::optional< Symbol > GetSymbol(uint32_t address) const
Get full symbol info for an address.
std::optional< Symbol > FindSymbol(const std::string &name) const
Find symbol by name.
std::vector< Symbol > FindSymbolsMatching(const std::string &pattern) const
Find symbols matching a pattern (supports wildcards)
std::string GetExtension(const std::string &path)
bool WildcardMatch(const std::string &pattern, const std::string &str)
bool IsValidLabelName(const std::string &name)
absl::StatusOr< std::string > ReadFileContent(const std::string &path)
std::string GetFilename(const std::string &path)
std::optional< uint32_t > ParseAddress(const std::string &str)
SymbolFormat
Supported symbol file formats.
Information about a symbol (label, constant, or address)