7#include "absl/strings/str_format.h"
8#include "absl/strings/str_join.h"
9#include "absl/strings/str_split.h"
22 return absl::StrFormat(
"$%02X:%04X", (address >> 16) & 0xFF, address & 0xFFFF);
28 static const std::set<uint8_t> branch_jump_opcodes = {
48 return branch_jump_opcodes.count(opcode) > 0;
53 return opcode == 0x60 ||
60 static const std::set<uint8_t> stack_opcodes = {
74 return stack_opcodes.count(opcode) > 0;
80 : emulator_service_(emulator_service),
82 symbol_provider_(std::make_unique<
yaze::emu::debug::SymbolProvider>()) {
88 const yaze::agent::BreakpointHitResponse& hit) {
92 if (hit.has_breakpoint()) {
93 analysis.
address = hit.breakpoint().address();
95 analysis.
address = hit.cpu_state().pc();
106 const auto& cpu = hit.cpu_state();
117 grpc::ServerContext context;
118 yaze::agent::DisassemblyRequest disasm_req;
119 disasm_req.set_start_address(analysis.
address);
120 disasm_req.set_count(1);
121 yaze::agent::DisassemblyResponse disasm_resp;
125 return absl::InternalError(
"Failed to get disassembly");
128 if (disasm_resp.lines_size() > 0) {
129 const auto& inst = disasm_resp.lines(0);
130 analysis.
disassembly = inst.mnemonic() +
" " + inst.operand_str();
134 if (explanation.ok()) {
148 analysis.
suggestions.push_back(issue->suggested_fix);
155 uint32_t address,
size_t length) {
161 grpc::ServerContext context;
162 MemoryRequest mem_req;
163 mem_req.set_address(address);
164 mem_req.set_size(length);
165 MemoryResponse mem_resp;
169 return absl::InternalError(
"Failed to read memory");
172 analysis.
data.assign(mem_resp.data().begin(), mem_resp.data().end());
179 if (struct_info.has_value()) {
190 analysis.
fields[
"sprite_index"] = sprite_index;
192 if (length >= 0x10) {
193 analysis.
fields[
"state"] = analysis.
data[0x00];
194 analysis.
fields[
"x_pos_low"] = analysis.
data[0x01];
195 analysis.
fields[
"x_pos_high"] = analysis.
data[0x02];
196 analysis.
fields[
"y_pos_low"] = analysis.
data[0x03];
197 analysis.
fields[
"y_pos_high"] = analysis.
data[0x04];
200 if (analysis.
data[0x00] == 0x00) {
201 analysis.
anomalies.push_back(
"Sprite is inactive (state = 0)");
203 if (analysis.
data[0x02] > 0x01) {
204 analysis.
anomalies.push_back(
"Sprite X position exceeds screen bounds");
210 analysis.
fields[
"current_health"] = analysis.
data[0];
211 if (analysis.
data[0] == 0) {
212 analysis.
anomalies.push_back(
"Player health is zero - death state");
215 analysis.
fields[
"max_health"] = analysis.
data[0];
218 analysis.
fields[
"inventory_slot"] = item_slot;
219 analysis.
fields[
"item_value"] = analysis.
data[0];
224 analysis.
fields[
"dma_channel"] = dma_channel;
229 analysis.
fields[
"dma_control"] = analysis.
data[0];
232 analysis.
fields[
"dma_destination"] = analysis.
data[0];
247 analysis.
anomalies.push_back(
"DMA channel enabled during active display - may cause glitches");
252 if (analysis.
data_type ==
"sprite" && length >= 16) {
254 bool all_zero = std::all_of(analysis.
data.begin(),
255 analysis.
data.begin() + std::min(
size_t(16), length),
256 [](uint8_t b) { return b == 0; });
257 bool all_ff = std::all_of(analysis.
data.begin(),
258 analysis.
data.begin() + std::min(
size_t(16), length),
259 [](uint8_t b) { return b == 0xFF; });
262 analysis.
anomalies.push_back(
"Sprite data is all zeros - likely uninitialized");
265 analysis.
anomalies.push_back(
"Sprite data is all 0xFF - possible corruption");
273 const std::vector<ExecutionTraceBuffer::TraceEntry>& trace) {
274 std::stringstream explanation;
276 explanation <<
"Execution Trace Analysis:\n";
277 explanation <<
"=========================\n\n";
280 std::vector<std::string> call_stack;
281 int indent_level = 0;
283 for (
size_t i = 0; i < trace.size(); ++i) {
284 const auto& entry = trace[i];
287 std::string indent(indent_level * 2,
' ');
290 if (entry.opcode == 0x20 || entry.opcode == 0x22 || entry.opcode == 0xFC) {
292 uint32_t target_addr = 0;
293 if (entry.operands.size() >= 2) {
294 target_addr = entry.operands[0] | (entry.operands[1] << 8);
295 if (entry.opcode == 0x22 && entry.operands.size() >= 3) {
296 target_addr |= (entry.operands[2] << 16);
302 : FormatSnesAddress(target_addr);
304 explanation << indent <<
"→ CALL " << target_name <<
" from "
305 << FormatSnesAddress(entry.address) <<
"\n";
307 call_stack.push_back(target_name);
310 }
else if (IsReturn(entry.opcode)) {
312 if (!call_stack.empty()) {
313 indent_level = std::max(0, indent_level - 1);
314 indent = std::string(indent_level * 2,
' ');
315 explanation << indent <<
"← RETURN from " << call_stack.back() <<
"\n";
316 call_stack.pop_back();
319 }
else if (IsBranchOrJump(entry.opcode)) {
321 std::string condition;
322 switch (entry.opcode) {
323 case 0x10: condition =
" (if plus)";
break;
324 case 0x30: condition =
" (if minus)";
break;
325 case 0x50: condition =
" (if overflow clear)";
break;
326 case 0x70: condition =
" (if overflow set)";
break;
327 case 0x90: condition =
" (if carry clear)";
break;
328 case 0xB0: condition =
" (if carry set)";
break;
329 case 0xD0: condition =
" (if not zero)";
break;
330 case 0xF0: condition =
" (if zero)";
break;
334 case 0x5C: condition =
" (unconditional)";
break;
337 explanation << indent <<
" " << entry.mnemonic <<
" " << entry.operand_str
338 << condition <<
"\n";
342 bool is_significant =
false;
346 if (entry.mnemonic.find(
"LD") != std::string::npos ||
347 entry.mnemonic.find(
"ST") != std::string::npos) {
348 is_significant =
true;
351 if (entry.operands.size() >= 2) {
352 uint32_t addr = entry.operands[0] | (entry.operands[1] << 8);
353 if (entry.operands.size() >= 3) {
354 addr |= (entry.operands[2] << 16);
361 if (ModifiesStack(entry.opcode)) {
362 is_significant =
true;
364 if (entry.opcode == 0x48 || entry.opcode == 0x08) {
366 }
else if (entry.opcode == 0x68 || entry.opcode == 0x28) {
372 if (entry.mnemonic.find(
"CMP") != std::string::npos ||
373 entry.mnemonic.find(
"BIT") != std::string::npos ||
374 entry.mnemonic.find(
"TST") != std::string::npos) {
375 is_significant =
true;
379 if (is_significant) {
380 explanation << indent <<
" " << FormatSnesAddress(entry.address)
381 <<
": " << entry.mnemonic <<
" " << entry.operand_str
389 if (entry.address == trace[i-1].address && IsBranchOrJump(entry.opcode)) {
390 explanation << indent <<
" ⚠️ POSSIBLE INFINITE LOOP DETECTED\n";
397 explanation << indent <<
" ⚠️ RAPID DMA OPERATIONS - CHECK TIMING\n";
404 explanation <<
"\nSummary:\n";
405 explanation <<
"--------\n";
406 explanation <<
"Total instructions: " << trace.size() <<
"\n";
407 explanation <<
"Subroutine depth: " << indent_level <<
"\n";
408 if (!call_stack.empty()) {
409 explanation <<
"Unmatched calls: " << absl::StrJoin(call_stack,
", ") <<
"\n";
412 return explanation.str();
416 uint32_t address,
size_t length,
const std::vector<uint8_t>& original) {
424 grpc::ServerContext context;
425 MemoryRequest mem_req;
426 mem_req.set_address(address);
427 mem_req.set_size(length);
428 MemoryResponse mem_resp;
432 return absl::InternalError(
"Failed to read patched memory");
435 result.
patched_code.assign(mem_resp.data().begin(), mem_resp.data().end());
438 std::stringstream orig_disasm, patch_disasm;
439 size_t orig_offset = 0, patch_offset = 0;
441 while (orig_offset < original.size() && patch_offset < result.
patched_code.size()) {
443 std::string orig_mnem, orig_operand;
444 std::vector<uint8_t> orig_operands;
446 address + orig_offset,
447 original.data() + orig_offset,
448 orig_mnem, orig_operand, orig_operands);
450 orig_disasm << FormatSnesAddress(address + orig_offset) <<
": "
451 << orig_mnem <<
" " << orig_operand <<
"\n";
454 std::string patch_mnem, patch_operand;
455 std::vector<uint8_t> patch_operands;
457 address + patch_offset,
459 patch_mnem, patch_operand, patch_operands);
461 patch_disasm << FormatSnesAddress(address + patch_offset) <<
": "
462 << patch_mnem <<
" " << patch_operand <<
"\n";
465 if (orig_mnem != patch_mnem || orig_operand != patch_operand) {
467 "At %s: '%s %s' → '%s %s'",
468 FormatSnesAddress(address + orig_offset),
469 orig_mnem, orig_operand,
470 patch_mnem, patch_operand));
475 if (IsBranchOrJump(result.
patched_code[patch_offset])) {
477 if (patch_operands.size() >= 2) {
478 target = patch_operands[0] | (patch_operands[1] << 8);
479 if (patch_operands.size() >= 3) {
480 target |= (patch_operands[2] << 16);
486 "Invalid jump target at %s: %s",
487 FormatSnesAddress(address + patch_offset),
488 FormatSnesAddress(target)));
494 bool orig_modifies_stack = ModifiesStack(original[orig_offset]);
495 bool patch_modifies_stack = ModifiesStack(result.
patched_code[patch_offset]);
497 if (orig_modifies_stack != patch_modifies_stack) {
499 "Stack modification mismatch - may cause stack imbalance");
504 orig_offset += orig_size;
505 patch_offset += patch_size;
516 "Patch modifies critical system area - verify this is intentional");
529 "Patch contains %d NOPs - possible padding or removed code", nop_count));
533 for (
size_t i = 0; i < result.
patched_code.size(); ++i) {
536 "BRK instruction at %s - usually indicates error",
537 FormatSnesAddress(address + i)));
546 uint32_t start_address, uint32_t end_address) {
547 std::vector<DetectedIssue> issues;
550 grpc::ServerContext context;
551 MemoryRequest mem_req;
552 mem_req.set_address(start_address);
553 mem_req.set_size(end_address - start_address);
554 MemoryResponse mem_resp;
561 const uint8_t* code =
reinterpret_cast<const uint8_t*
>(mem_resp.data().data());
562 size_t code_size = mem_resp.data().size();
565 while (offset < code_size) {
566 uint32_t current_addr = start_address + offset;
570 if (issue.has_value()) {
571 issues.push_back(issue.value());
575 uint8_t inst_size =
disassembler_->GetInstructionSize(code[offset]);
576 if (inst_size == 0) {
581 absl::StrFormat(
"Invalid opcode $%02X at %s",
582 code[offset], FormatSnesAddress(current_addr)),
583 "Check if this is data being executed as code",
597 if (address < 0x008000) {
601 if (address >= 0x008000 && address < 0x7E0000) {
609 if (address >= 0x800000 && address < 0xC00000) {
620 grpc::ServerContext context;
621 MemoryRequest mem_req;
622 mem_req.set_address(routine_start);
623 mem_req.set_size(routine_end - routine_start);
624 MemoryResponse mem_resp;
631 const uint8_t* code =
reinterpret_cast<const uint8_t*
>(mem_resp.data().data());
632 size_t code_size = mem_resp.data().size();
638 while (offset < code_size) {
639 uint8_t opcode = code[offset];
670 if (IsReturn(opcode)) {
672 return stack_depth != 0;
675 uint8_t inst_size =
disassembler_->GetInstructionSize(opcode);
676 offset += (inst_size > 0) ? inst_size : 1;
680 return stack_depth != 0;
685 uint32_t end_address = address + length;
688 if ((address <= 0x00FFFF && end_address > 0x00FFE0)) {
693 if (address <= NMI_FLAG && end_address >
NMI_FLAG) {
698 if (address >= 0x002100 && address <= 0x00213F) {
711 if (address >= 0x7E0000 && address < 0x7E2000) {
713 if (address < 0x7E0100) {
731 if (address < 0x100) {
733 }
else if (address >= 0x100 && address < 0x200) {
757 uint32_t sprite_num = offset / 0x10;
758 description = absl::StrFormat(
"Sprite %d Data", sprite_num);
767 description = absl::StrFormat(
"DMA Channel %d", channel);
802 else if (address >= 0x008000 && address < 0x00FFFF) {
803 description = absl::StrFormat(
"ROM Bank $%02X", (address >> 16) & 0xFF);
806 }
else if (address >= 0x800000 && address < 0xC00000) {
807 description = absl::StrFormat(
"Extended ROM Bank $%02X", (address >> 16) & 0xFF);
831 }
else if (address >= 0x002100 && address <= 0x00213F) {
839 }
else if (address >= 0x008000 && address < 0x7E0000) {
852 const std::map<std::string, uint16_t>& regs)
const {
853 std::stringstream ss;
855 ss << absl::StrFormat(
"A=%04X ", regs.at(
"A"));
856 ss << absl::StrFormat(
"X=%04X ", regs.at(
"X"));
857 ss << absl::StrFormat(
"Y=%04X ", regs.at(
"Y"));
858 ss << absl::StrFormat(
"S=%04X ", regs.at(
"S"));
859 ss << absl::StrFormat(
"PC=%04X ", regs.at(
"PC"));
860 ss << absl::StrFormat(
"P=%02X ", regs.at(
"P"));
861 ss << absl::StrFormat(
"DB=%02X ", regs.at(
"DB"));
862 ss << absl::StrFormat(
"PB=%02X", regs.at(
"PB"));
877 uint32_t address,
const uint8_t* code,
size_t max_length) {
878 if (max_length == 0) {
879 return absl::InvalidArgumentError(
"No code provided");
882 uint8_t opcode = code[0];
883 std::string explanation;
888 case 0xA9: explanation =
"Load immediate value into accumulator";
break;
889 case 0xA2: explanation =
"Load immediate value into X register";
break;
890 case 0xA0: explanation =
"Load immediate value into Y register";
break;
891 case 0xAD: explanation =
"Load accumulator from absolute address";
break;
892 case 0xAE: explanation =
"Load X register from absolute address";
break;
893 case 0xAC: explanation =
"Load Y register from absolute address";
break;
896 case 0x8D: explanation =
"Store accumulator to absolute address";
break;
897 case 0x8E: explanation =
"Store X register to absolute address";
break;
898 case 0x8C: explanation =
"Store Y register to absolute address";
break;
899 case 0x9D: explanation =
"Store accumulator to address indexed by X";
break;
900 case 0x99: explanation =
"Store accumulator to address indexed by Y";
break;
903 case 0x10: explanation =
"Branch if plus (N flag clear)";
break;
904 case 0x30: explanation =
"Branch if minus (N flag set)";
break;
905 case 0x50: explanation =
"Branch if overflow clear";
break;
906 case 0x70: explanation =
"Branch if overflow set";
break;
907 case 0x80: explanation =
"Branch always";
break;
908 case 0x90: explanation =
"Branch if carry clear (less than)";
break;
909 case 0xB0: explanation =
"Branch if carry set (greater than or equal)";
break;
910 case 0xD0: explanation =
"Branch if not equal (Z flag clear)";
break;
911 case 0xF0: explanation =
"Branch if equal (Z flag set)";
break;
914 case 0x20: explanation =
"Call subroutine";
break;
915 case 0x22: explanation =
"Call long subroutine (24-bit)";
break;
916 case 0x4C: explanation =
"Jump to address";
break;
917 case 0x5C: explanation =
"Jump long (24-bit)";
break;
918 case 0x60: explanation =
"Return from subroutine";
break;
919 case 0x6B: explanation =
"Return from long subroutine";
break;
922 case 0x48: explanation =
"Push accumulator onto stack";
break;
923 case 0x68: explanation =
"Pull accumulator from stack";
break;
924 case 0x08: explanation =
"Push processor status onto stack";
break;
925 case 0x28: explanation =
"Pull processor status from stack";
break;
928 case 0x69: explanation =
"Add to accumulator with carry";
break;
929 case 0xE9: explanation =
"Subtract from accumulator with borrow";
break;
930 case 0xC9: explanation =
"Compare accumulator with value";
break;
931 case 0xE0: explanation =
"Compare X register with value";
break;
932 case 0xC0: explanation =
"Compare Y register with value";
break;
935 case 0x29: explanation =
"AND accumulator with value";
break;
936 case 0x09: explanation =
"OR accumulator with value";
break;
937 case 0x49: explanation =
"XOR accumulator with value";
break;
940 case 0x00: explanation =
"Software interrupt (BRK)";
break;
941 case 0xEA: explanation =
"No operation (NOP)";
break;
942 case 0x18: explanation =
"Clear carry flag";
break;
943 case 0x38: explanation =
"Set carry flag";
break;
944 case 0xC2: explanation =
"Clear processor flags (REP)";
break;
945 case 0xE2: explanation =
"Set processor flags (SEP)";
break;
948 explanation = absl::StrFormat(
"Execute opcode $%02X", opcode);
955 uint32_t address,
int before_lines,
int after_lines) {
956 std::vector<std::string> context_lines;
959 grpc::ServerContext ctx;
960 DisassemblyRequest req;
961 req.set_start_address(address - (before_lines * 3));
962 req.set_count(before_lines + after_lines + 1);
963 DisassemblyResponse resp;
967 return context_lines;
970 for (
const auto& inst : resp.lines()) {
971 std::string line = absl::StrFormat(
"%s: %s %s",
972 FormatSnesAddress(inst.address()),
975 if (inst.address() == address) {
976 line =
">>> " + line +
" <<<";
978 context_lines.push_back(line);
981 return context_lines;
985 std::vector<std::string> stack;
988 grpc::ServerContext ctx;
989 yaze::agent::TraceRequest req;
990 req.set_max_entries(100);
991 yaze::agent::TraceResponse resp;
999 for (
int i = resp.entries_size() - 1; i >= 0; --i) {
1000 const auto& entry = resp.entries(i);
1001 uint8_t opcode = entry.opcode();
1003 if (opcode == 0x20 || opcode == 0x22 || opcode == 0xFC) {
1007 : FormatSnesAddress(entry.address());
1008 stack.push_back(caller);
1009 }
else if (IsReturn(opcode) && !stack.empty()) {
1016 std::reverse(stack.begin(), stack.end());
1022 uint32_t address,
const uint8_t* code,
size_t length) {
1024 return std::nullopt;
1027 uint8_t opcode = code[0];
1030 if (opcode == 0x00) {
1034 "BRK instruction found - usually indicates an error or unimplemented code",
1035 "Replace with proper implementation or NOP if intentional padding",
1041 if (opcode == 0x80 && length >= 2 && code[1] == 0xFE) {
1046 "Infinite loop detected (BRA to self)",
1047 "Add proper exit condition or loop counter",
1053 if (opcode == 0x8D && length >= 3) {
1054 uint16_t dest = code[1] | (code[2] << 8);
1055 if (dest >= 0xFFE0) {
1059 absl::StrFormat(
"Writing to interrupt vector at $%04X", dest),
1060 "Verify this vector modification is intentional",
1067 if (ModifiesStack(opcode)) {
1070 int consecutive_pushes = 0;
1071 for (
size_t i = 0; i < std::min(
size_t(10), length); ++i) {
1072 if (code[i] == 0x48 || code[i] == 0x08 || code[i] == 0xDA || code[i] == 0x5A) {
1073 consecutive_pushes++;
1074 }
else if (code[i] == 0x68 || code[i] == 0x28 || code[i] == 0xFA || code[i] == 0x7A) {
1075 consecutive_pushes--;
1079 if (consecutive_pushes > 5) {
1083 "Multiple consecutive pushes without pulls - possible stack overflow",
1084 "Verify stack operations are properly balanced",
1090 return std::nullopt;
1095 if (address < 0x200) {
1100 if (address >= 0xFFE0 && address <= 0xFFFF) {
1122 case 0x00:
return "Sprite State";
1123 case 0x01:
return "Sprite X Position Low";
1124 case 0x02:
return "Sprite X Position High";
1125 case 0x03:
return "Sprite Y Position Low";
1126 case 0x04:
return "Sprite Y Position High";
1127 case 0x05:
return "Sprite Z Position";
1128 case 0x06:
return "Sprite Velocity X";
1129 case 0x07:
return "Sprite Velocity Y";
1130 case 0x08:
return "Sprite Type";
1131 case 0x09:
return "Sprite Subtype";
1132 case 0x0A:
return "Sprite Graphics";
1133 case 0x0B:
return "Sprite Properties";
1134 case 0x0C:
return "Sprite Health";
1135 case 0x0D:
return "Sprite Damage";
1136 case 0x0E:
return "Sprite Timer";
1137 case 0x0F:
return "Sprite Flags";
1143 uint32_t offset = (address -
OAM_BUFFER) % 4;
1145 case 0:
return "OAM X Position";
1146 case 1:
return "OAM Y Position";
1147 case 2:
return "OAM Tile Number";
1148 case 3:
return "OAM Attributes";
1156 case 0x00:
return "DMA Control";
1157 case 0x01:
return "DMA Destination";
1158 case 0x02:
return "DMA Source Low";
1159 case 0x03:
return "DMA Source High";
1160 case 0x04:
return "DMA Source Bank";
1161 case 0x05:
return "DMA Size Low";
1162 case 0x06:
return "DMA Size High";
1163 case 0x07:
return "DMA Indirect Bank";
1167 return std::nullopt;
grpc::Status GetExecutionTrace(grpc::ServerContext *context, const TraceRequest *request, TraceResponse *response) override
grpc::Status GetDisassembly(grpc::ServerContext *context, const DisassemblyRequest *request, DisassemblyResponse *response) override
grpc::Status ReadMemory(grpc::ServerContext *context, const MemoryRequest *request, MemoryResponse *response) override
static constexpr uint32_t FRAME_COUNTER
void SetOriginalRom(const std::vector< uint8_t > &rom_data)
Set the original ROM data for comparison.
static constexpr uint32_t OAM_BUFFER
static constexpr uint32_t PLAYER_HEALTH
static constexpr uint32_t LINK_Y_POS
static constexpr uint32_t OAM_BUFFER_END
static constexpr uint32_t DMA_ENABLE
absl::StatusOr< std::string > ExplainExecutionTrace(const std::vector< ExecutionTraceBuffer::TraceEntry > &trace)
Analyze execution trace and explain program flow.
static constexpr uint32_t WRAM_END
absl::StatusOr< BreakpointAnalysis > AnalyzeBreakpoint(const yaze::agent::BreakpointHitResponse &hit)
Analyze a breakpoint hit with full context.
static constexpr uint32_t APU_PORT0
bool IsMemoryWriteSafe(uint32_t address, size_t length) const
Check if memory write is safe.
static constexpr uint32_t PPU_BGMODE
static constexpr uint32_t LINK_X_POS
std::string FormatRegisterState(const std::map< std::string, uint16_t > ®s) const
Format register state for debugging output.
static constexpr uint32_t SPRITE_TABLE_START
bool IsValidJumpTarget(uint32_t address) const
Check if an address is a valid jump target.
static constexpr uint32_t PPU_CGADD
static constexpr uint32_t INVENTORY_START
static constexpr uint32_t GAME_MODE
RomDebugAgent(yaze::agent::EmulatorServiceImpl *emulator_service)
yaze::agent::EmulatorServiceImpl * emulator_service_
static constexpr uint32_t SRAM_END
std::optional< std::string > GetStructureInfo(uint32_t address) const
Get structure information for a memory address.
std::map< uint32_t, std::string > address_description_cache_
std::vector< DetectedIssue > ScanForIssues(uint32_t start_address, uint32_t end_address)
Scan for common ROM hacking issues in a code region.
static constexpr uint32_t SRAM_START
std::vector< std::string > GetDisassemblyContext(uint32_t address, int before_lines, int after_lines)
Get surrounding context for an address.
std::vector< std::string > BuildCallStack(uint32_t current_pc)
Build call stack from execution trace.
bool HasStackImbalance(uint32_t routine_start, uint32_t routine_end)
Detect stack imbalance in a subroutine.
std::vector< uint8_t > original_rom_
std::map< uint32_t, std::string > data_type_cache_
static constexpr uint32_t APU_PORT3
absl::Status LoadSymbols(const std::string &symbol_file)
Load symbol table for better disassembly.
absl::StatusOr< PatchComparisonResult > ComparePatch(uint32_t address, size_t length, const std::vector< uint8_t > &original)
Compare original ROM code with patched code.
std::unique_ptr< yaze::emu::debug::SymbolProvider > symbol_provider_
std::optional< DetectedIssue > DetectIssuePattern(uint32_t address, const uint8_t *code, size_t length)
Detect pattern of common issues.
static constexpr uint32_t SPRITE_TABLE_END
std::string DescribeMemoryLocation(uint32_t address) const
Get human-readable description of a memory address.
static constexpr uint32_t SUBMODULE
absl::StatusOr< std::string > AnalyzeInstruction(uint32_t address, const uint8_t *code, size_t max_length)
Analyze the instruction at an address.
static constexpr uint32_t PPU_INIDISP
bool IsCriticalMemoryArea(uint32_t address) const
Check if address is in a critical system area.
static constexpr uint32_t NMI_FLAG
absl::StatusOr< MemoryAnalysis > AnalyzeMemory(uint32_t address, size_t length)
Analyze a memory region and identify its purpose.
static constexpr uint32_t LINK_STATE
static constexpr uint32_t PLAYER_NAME
static constexpr uint32_t HDMA_ENABLE
static constexpr uint32_t WRAM_START
static constexpr uint32_t PPU_CGDATA
static constexpr uint32_t PLAYER_MAX_HEALTH
static constexpr uint32_t DMA0_CONTROL
std::unique_ptr< Disassembler65816 > disassembler_
static constexpr uint32_t LINK_DIRECTION
std::string IdentifyDataType(uint32_t address) const
Get the data type at a memory address.
bool IsReturn(uint8_t opcode)
std::string FormatSnesAddress(uint32_t address)
bool ModifiesStack(uint8_t opcode)
bool IsBranchOrJump(uint8_t opcode)
Analysis result for a breakpoint hit.
std::map< std::string, uint16_t > registers
std::vector< std::string > context_lines
std::string location_description
std::vector< std::string > suggestions
std::string instruction_explanation
std::vector< std::string > call_stack
Detected issue in ROM execution.
Analysis of a memory region.
std::string structure_name
std::vector< std::string > anomalies
std::vector< uint8_t > data
std::map< std::string, uint32_t > fields
Results from patch comparison.
std::vector< std::string > differences
std::vector< std::string > potential_issues
std::vector< uint8_t > patched_code
std::vector< uint8_t > original_code
std::string patched_disassembly
std::string original_disassembly