yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
memory_inspector_tool.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <iomanip>
6#include <sstream>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
11
12namespace yaze {
13namespace cli {
14namespace agent {
15namespace tools {
16
17namespace {
18
19// Game mode descriptions
20const std::map<uint8_t, std::string> kGameModes = {
21 {0x00, "Reset/Logo"},
22 {0x01, "Title Screen"},
23 {0x02, "File Select"},
24 {0x03, "Copy/Erase"},
25 {0x04, "Name Entry"},
26 {0x05, "Loading Area"},
27 {0x06, "Pre-Dungeon"},
28 {0x07, "Dungeon"},
29 {0x08, "Pre-Overworld"},
30 {0x09, "Overworld"},
31 {0x0A, "Pre-Overworld (Special)"},
32 {0x0B, "Overworld (Special)"},
33 {0x0C, "Blank Screen"},
34 {0x0D, "Text (Dialogue)"},
35 {0x0E, "Closing Spotlight"},
36 {0x0F, "Opening Spotlight"},
37 {0x10, "Spotlight (Other)"},
38 {0x11, "Dungeon Spotlight"},
39 {0x12, "Dungeon Endgame"},
40 {0x13, "Ganon Emerging"},
41 {0x14, "Ganon Phase 1"},
42 {0x15, "Ganon Phase 2"},
43 {0x16, "Triforce Room"},
44 {0x17, "Ending Sequence"},
45 {0x18, "Map Screen"},
46 {0x19, "Inventory"},
47 {0x1A, "Red Screen"},
48 {0x1B, "Attract Mode"},
49};
50
51// Sprite type names (common ones)
52const std::map<uint8_t, std::string> kSpriteTypes = {
53 {0x00, "Raven"},
54 {0x01, "Vulture"},
55 {0x02, "Flying Tile"},
56 {0x03, "Empty"},
57 {0x04, "Pull Switch"},
58 {0x05, "Octorock"},
59 {0x06, "Wall Master"},
60 {0x07, "Moldorm (tail)"},
61 {0x08, "Octorock (4)"},
62 {0x09, "Chicken"},
63 {0x0A, "Octorok (Stone)"},
64 {0x0B, "Buzzblob"},
65 {0x0C, "Snap Dragon"},
66 {0x0D, "Octoballoon"},
67 {0x0E, "Octoballoon Hatchling"},
68 {0x0F, "Hinox"},
69 {0x10, "Moblin"},
70 {0x11, "Mini Helmasaur"},
71 {0x12, "Gargoyle's Domain (Fireball)"},
72 {0x13, "Antifairy"},
73 {0x14, "Sahasrahla/Elder"},
74 {0x15, "Bush Hoarder"},
75};
76
77// Link direction names
78const std::map<uint8_t, std::string> kLinkDirections = {
79 {0x00, "North"},
80 {0x02, "South"},
81 {0x04, "West"},
82 {0x06, "East"},
83};
84
85} // namespace
86
87// ============================================================================
88// MemoryInspectorBase Implementation
89// ============================================================================
90
91std::string MemoryInspectorBase::DescribeAddress(uint32_t address) const {
92 // Check known regions
93 if (address == ALTTPMemoryMap::kGameMode)
94 return "Game Mode";
95 if (address == ALTTPMemoryMap::kSubmodule)
96 return "Submodule";
97 if (address == ALTTPMemoryMap::kFrameCounter)
98 return "Frame Counter";
99 if (address == ALTTPMemoryMap::kLinkXLow ||
101 return "Link X Position";
102 if (address == ALTTPMemoryMap::kLinkYLow ||
104 return "Link Y Position";
105 if (address == ALTTPMemoryMap::kLinkState)
106 return "Link State";
107 if (address == ALTTPMemoryMap::kLinkDirection)
108 return "Link Direction";
109 if (address == ALTTPMemoryMap::kOverworldArea)
110 return "Overworld Area";
111 if (address == ALTTPMemoryMap::kDungeonRoom)
112 return "Dungeon Room";
113 if (address == ALTTPMemoryMap::kPlayerHealth)
114 return "Player Health";
116 return "Player Max Health";
117 if (address == ALTTPMemoryMap::kPlayerRupees)
118 return "Player Rupees";
119
120 // Check ranges
121 if (ALTTPMemoryMap::IsSpriteTable(address)) {
122 int offset = address - 0x7E0D00;
123 int sprite_index = offset % 16;
124 return absl::StrFormat("Sprite Table (Sprite %d)", sprite_index);
125 }
126 if (ALTTPMemoryMap::IsSaveData(address)) {
127 return "Save Data / Inventory";
128 }
129 if (address >= ALTTPMemoryMap::kOAMBuffer &&
131 return "OAM Buffer";
132 }
133 if (ALTTPMemoryMap::IsWRAM(address)) {
134 return "WRAM";
135 }
136
137 return "Unknown";
138}
139
140std::string MemoryInspectorBase::IdentifyDataType(uint32_t address) const {
142 return "sprite_table";
143 if (ALTTPMemoryMap::IsSaveData(address))
144 return "save_data";
145 if (address >= ALTTPMemoryMap::kOAMBuffer &&
147 return "oam_buffer";
148 if (address == ALTTPMemoryMap::kGameMode ||
150 return "game_state";
151 if (address >= ALTTPMemoryMap::kLinkXLow &&
153 return "player_state";
154 return "generic";
155}
156
157std::vector<MemoryRegionInfo> MemoryInspectorBase::GetKnownRegions() const {
158 return {
159 {"game_mode", "Current game mode/state", ALTTPMemoryMap::kGameMode,
161 {"submodule", "Game submodule state", ALTTPMemoryMap::kSubmodule,
163 {"frame_counter", "Frame counter", ALTTPMemoryMap::kFrameCounter,
165 {"link_position", "Link's X/Y position", ALTTPMemoryMap::kLinkYLow,
167 {"link_state", "Link's animation state", ALTTPMemoryMap::kLinkState,
169 {"link_direction", "Link's facing direction",
171 {"sprite_y_low", "Sprite Y positions (low byte)",
173 {"sprite_x_low", "Sprite X positions (low byte)",
175 {"sprite_state", "Sprite states", ALTTPMemoryMap::kSpriteState,
176 ALTTPMemoryMap::kSpriteState + 15, "array"},
177 {"sprite_type", "Sprite types", ALTTPMemoryMap::kSpriteType,
178 ALTTPMemoryMap::kSpriteType + 15, "array"},
179 {"oam_buffer", "OAM sprite buffer", ALTTPMemoryMap::kOAMBuffer,
181 {"overworld_area", "Current overworld area",
183 {"dungeon_room", "Current dungeon room", ALTTPMemoryMap::kDungeonRoom,
184 ALTTPMemoryMap::kDungeonRoom + 1, "word"},
185 {"player_health", "Player current health", ALTTPMemoryMap::kPlayerHealth,
187 {"player_max_health", "Player max health",
189 "byte"},
190 {"player_rupees", "Player rupees", ALTTPMemoryMap::kPlayerRupees,
192 {"inventory", "Player inventory", ALTTPMemoryMap::kInventoryStart,
193 ALTTPMemoryMap::kInventoryStart + 0x2F, "struct"},
194 };
195}
196
197std::string MemoryInspectorBase::FormatHex(const std::vector<uint8_t>& data,
198 int bytes_per_line) const {
199 std::ostringstream oss;
200 for (size_t i = 0; i < data.size(); ++i) {
201 if (i > 0 && i % bytes_per_line == 0)
202 oss << "\n";
203 else if (i > 0)
204 oss << " ";
205 oss << absl::StrFormat("%02X", data[i]);
206 }
207 return oss.str();
208}
209
211 const std::vector<uint8_t>& data) const {
212 std::string result;
213 result.reserve(data.size());
214 for (uint8_t byte : data) {
215 result += (std::isprint(byte) ? static_cast<char>(byte) : '.');
216 }
217 return result;
218}
219
220absl::StatusOr<uint32_t> MemoryInspectorBase::ParseAddress(
221 const std::string& addr_str) const {
222 std::string s = addr_str;
223
224 // Remove $ or 0x prefix
225 if (!s.empty() && s[0] == '$') {
226 s = s.substr(1);
227 } else if (s.size() > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
228 s = s.substr(2);
229 }
230
231 // Parse as hex
232 try {
233 return static_cast<uint32_t>(std::stoul(s, nullptr, 16));
234 } catch (const std::exception& e) {
235 return absl::InvalidArgumentError(
236 absl::StrCat("Invalid address: ", addr_str));
237 }
238}
239
240// ============================================================================
241// MemoryAnalyzeTool Implementation
242// ============================================================================
243
245 const resources::ArgumentParser& parser) {
246 return parser.RequireArgs({"address", "length"});
247}
248
250 const resources::ArgumentParser& parser,
251 resources::OutputFormatter& formatter) {
252 auto addr_str = parser.GetString("address");
253 if (!addr_str.has_value()) {
254 return absl::InvalidArgumentError("Missing required argument: address");
255 }
256 auto addr_result = ParseAddress(*addr_str);
257 if (!addr_result.ok())
258 return addr_result.status();
259 uint32_t address = *addr_result;
260
261 int length = std::stoi(parser.GetString("length").value_or("16"));
262 if (length <= 0 || length > 0x10000) {
263 return absl::InvalidArgumentError("Length must be between 1 and 65536");
264 }
265
266 // Build analysis result
267 formatter.BeginObject("MemoryAnalysis");
268 formatter.AddField("address", absl::StrFormat("$%06X", address));
269 formatter.AddField("length", length);
270 formatter.AddField("region", DescribeAddress(address));
271 formatter.AddField("data_type", IdentifyDataType(address));
272 formatter.AddField("note",
273 "Connect to emulator via gRPC to read actual memory data");
274
275 // Provide context-specific analysis hints
276 if (ALTTPMemoryMap::IsSpriteTable(address)) {
277 formatter.AddField(
278 "analysis_hint",
279 "Sprite table: Check sprite_state ($DD0), sprite_type ($E20), "
280 "sprite_health ($E50) for each sprite (0-15)");
281 } else if (address >= ALTTPMemoryMap::kLinkYLow &&
283 formatter.AddField(
284 "analysis_hint",
285 "Player state: Position at $20-$23, state at $5D, direction at $2F");
286 } else if (address == ALTTPMemoryMap::kGameMode) {
287 formatter.AddField(
288 "analysis_hint",
289 "Game mode: 0x07=Dungeon, 0x09=Overworld, 0x19=Inventory, "
290 "0x0D=Dialogue");
291 }
292
293 formatter.EndObject();
294 return absl::OkStatus();
295}
296
297std::map<std::string, std::string> MemoryAnalyzeTool::AnalyzeSpriteEntry(
298 int sprite_index, const std::vector<uint8_t>& wram) const {
299 std::map<std::string, std::string> result;
300
301 if (wram.size() < 0x1000)
302 return result;
303
304 uint8_t state = wram[0x0DD0 + sprite_index];
305 uint8_t type = wram[0x0E20 + sprite_index];
306 uint8_t health = wram[0x0E50 + sprite_index];
307 uint8_t y_low = wram[0x0D00 + sprite_index];
308 uint8_t x_low = wram[0x0D10 + sprite_index];
309 uint8_t y_high = wram[0x0D20 + sprite_index];
310 uint8_t x_high = wram[0x0D30 + sprite_index];
311
312 uint16_t x = (x_high << 8) | x_low;
313 uint16_t y = (y_high << 8) | y_low;
314
315 result["sprite_index"] = std::to_string(sprite_index);
316 result["state"] = absl::StrFormat("$%02X", state);
317 result["active"] = (state != 0x00) ? "yes" : "no";
318 result["type"] = absl::StrFormat("$%02X", type);
319
320 auto type_it = kSpriteTypes.find(type);
321 if (type_it != kSpriteTypes.end()) {
322 result["type_name"] = type_it->second;
323 }
324
325 result["health"] = std::to_string(health);
326 result["position"] = absl::StrFormat("(%d, %d)", x, y);
327
328 return result;
329}
330
331std::map<std::string, std::string> MemoryAnalyzeTool::AnalyzePlayerState(
332 const std::vector<uint8_t>& wram) const {
333 std::map<std::string, std::string> result;
334
335 if (wram.size() < 0x100)
336 return result;
337
338 uint16_t x = (wram[0x23] << 8) | wram[0x22];
339 uint16_t y = (wram[0x21] << 8) | wram[0x20];
340 uint8_t state = wram[0x5D];
341 uint8_t direction = wram[0x2F];
342
343 result["position"] = absl::StrFormat("(%d, %d)", x, y);
344 result["state"] = absl::StrFormat("$%02X", state);
345
346 auto dir_it = kLinkDirections.find(direction);
347 if (dir_it != kLinkDirections.end()) {
348 result["direction"] = dir_it->second;
349 } else {
350 result["direction"] = absl::StrFormat("$%02X", direction);
351 }
352
353 return result;
354}
355
356std::map<std::string, std::string> MemoryAnalyzeTool::AnalyzeGameMode(
357 const std::vector<uint8_t>& wram) const {
358 std::map<std::string, std::string> result;
359
360 if (wram.size() < 0x20)
361 return result;
362
363 uint8_t mode = wram[0x10];
364 uint8_t submodule = wram[0x11];
365
366 result["mode"] = absl::StrFormat("$%02X", mode);
367 result["submodule"] = absl::StrFormat("$%02X", submodule);
368
369 auto mode_it = kGameModes.find(mode);
370 if (mode_it != kGameModes.end()) {
371 result["mode_name"] = mode_it->second;
372 }
373
374 return result;
375}
376
377// ============================================================================
378// MemorySearchTool Implementation
379// ============================================================================
380
382 const resources::ArgumentParser& parser) {
383 return parser.RequireArgs({"pattern"});
384}
385
387 const resources::ArgumentParser& parser,
388 resources::OutputFormatter& formatter) {
389 auto pattern_opt = parser.GetString("pattern");
390 if (!pattern_opt.has_value()) {
391 return absl::InvalidArgumentError("Missing required argument: pattern");
392 }
393 std::string pattern_str = *pattern_opt;
394 int max_results = std::stoi(parser.GetString("max-results").value_or("10"));
395
396 auto pattern_result = ParsePattern(pattern_str);
397 if (!pattern_result.ok())
398 return pattern_result.status();
399
400 auto [pattern, mask] = *pattern_result;
401
402 formatter.BeginObject("MemorySearch");
403 formatter.AddField("pattern", pattern_str);
404 formatter.AddField("pattern_length", static_cast<int>(pattern.size()));
405 formatter.AddField("max_results", max_results);
406 formatter.AddField("note",
407 "Connect to emulator via gRPC to search actual memory");
408
409 // Show parsed pattern
410 std::ostringstream parsed;
411 for (size_t i = 0; i < pattern.size(); ++i) {
412 if (i > 0)
413 parsed << " ";
414 if (mask[i]) {
415 parsed << absl::StrFormat("%02X", pattern[i]);
416 } else {
417 parsed << "??";
418 }
419 }
420 formatter.AddField("parsed_pattern", parsed.str());
421
422 formatter.EndObject();
423 return absl::OkStatus();
424}
425
426absl::StatusOr<std::pair<std::vector<uint8_t>, std::vector<bool>>>
427MemorySearchTool::ParsePattern(const std::string& pattern_str) const {
428 std::vector<uint8_t> pattern;
429 std::vector<bool> mask; // true = must match, false = wildcard
430
431 // Remove spaces and split into byte pairs
432 std::string clean;
433 for (char c : pattern_str) {
434 if (!std::isspace(c))
435 clean += c;
436 }
437
438 if (clean.length() % 2 != 0) {
439 return absl::InvalidArgumentError(
440 "Pattern must have even number of hex characters");
441 }
442
443 for (size_t i = 0; i < clean.length(); i += 2) {
444 std::string byte_str = clean.substr(i, 2);
445
446 if (byte_str == "??" || byte_str == "**") {
447 pattern.push_back(0x00);
448 mask.push_back(false); // Wildcard
449 } else {
450 try {
451 uint8_t byte = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
452 pattern.push_back(byte);
453 mask.push_back(true); // Must match
454 } catch (const std::exception&) {
455 return absl::InvalidArgumentError(
456 absl::StrCat("Invalid hex byte: ", byte_str));
457 }
458 }
459 }
460
461 return std::make_pair(pattern, mask);
462}
463
464std::vector<PatternMatch> MemorySearchTool::FindMatches(
465 const std::vector<uint8_t>& memory, uint32_t base_address,
466 const std::vector<uint8_t>& pattern, const std::vector<bool>& mask,
467 int max_results) const {
468 std::vector<PatternMatch> matches;
469
470 if (pattern.empty() || memory.size() < pattern.size())
471 return matches;
472
473 for (size_t i = 0; i <= memory.size() - pattern.size(); ++i) {
474 bool match = true;
475 for (size_t j = 0; j < pattern.size() && match; ++j) {
476 if (mask[j] && memory[i + j] != pattern[j]) {
477 match = false;
478 }
479 }
480
481 if (match) {
482 PatternMatch m;
483 m.address = base_address + static_cast<uint32_t>(i);
484 m.matched_bytes = std::vector<uint8_t>(
485 memory.begin() + i, memory.begin() + i + pattern.size());
487 matches.push_back(m);
488
489 if (static_cast<int>(matches.size()) >= max_results)
490 break;
491 }
492 }
493
494 return matches;
495}
496
497// ============================================================================
498// MemoryCompareTool Implementation
499// ============================================================================
500
502 const resources::ArgumentParser& parser) {
503 return parser.RequireArgs({"address"});
504}
505
507 const resources::ArgumentParser& parser,
508 resources::OutputFormatter& formatter) {
509 auto addr_result = ParseAddress(parser.GetString("address").value_or(""));
510 if (!addr_result.ok())
511 return addr_result.status();
512 uint32_t address = *addr_result;
513
514 std::string expected_str = parser.GetString("expected").value_or("");
515
516 formatter.BeginObject();
517 formatter.AddField("address", absl::StrFormat("$%06X", address));
518 formatter.AddField("region", DescribeAddress(address));
519
520 if (!expected_str.empty()) {
521 formatter.AddField("expected", expected_str);
522 formatter.AddField("note",
523 "Connect to emulator via gRPC to compare actual memory");
524 } else {
525 formatter.AddField(
526 "note", "Provide --expected <hex> to compare against expected values");
527 }
528
529 formatter.EndObject();
530 return absl::OkStatus();
531}
532
533// ============================================================================
534// MemoryCheckTool Implementation
535// ============================================================================
536
538 const resources::ArgumentParser& parser) {
539 return absl::OkStatus(); // No required args
540}
541
543 const resources::ArgumentParser& parser,
544 resources::OutputFormatter& formatter) {
545 std::string region = parser.GetString("region").value_or("all");
546
547 // Provide check descriptions
548 std::vector<std::string> checks;
549 if (region == "all" || region == "sprites") {
550 checks.push_back(
551 "Sprite table: Check for invalid states, out-of-bounds positions");
552 }
553 if (region == "all" || region == "player") {
554 checks.push_back(
555 "Player state: Check for invalid positions, corrupted state");
556 }
557 if (region == "all" || region == "game") {
558 checks.push_back(
559 "Game mode: Check for invalid mode/submodule combinations");
560 }
561
562 std::ostringstream checks_str;
563 for (const auto& check : checks) {
564 checks_str << "- " << check << "\n";
565 }
566
567 formatter.BeginObject();
568 formatter.AddField("region", region);
569 formatter.AddField("note",
570 "Connect to emulator via gRPC to check actual memory");
571 formatter.AddField("available_checks", checks_str.str());
572 formatter.EndObject();
573 return absl::OkStatus();
574}
575
576std::vector<MemoryAnomaly> MemoryCheckTool::CheckSpriteTable(
577 const std::vector<uint8_t>& wram) const {
578 std::vector<MemoryAnomaly> anomalies;
579
580 if (wram.size() < 0x1000)
581 return anomalies;
582
583 for (int i = 0; i < ALTTPMemoryMap::kMaxSprites; ++i) {
584 uint8_t state = wram[0x0DD0 + i];
585 uint8_t type = wram[0x0E20 + i];
586
587 // Check for invalid state (non-zero but unusually high)
588 if (state > 0 && state > 0x10) {
589 anomalies.push_back({
590 static_cast<uint32_t>(ALTTPMemoryMap::kSpriteState + i),
591 "suspicious_state",
592 absl::StrFormat("Sprite %d has unusual state $%02X", i, state),
593 2,
594 });
595 }
596
597 // Check for active sprite with type 0 (usually invalid)
598 if (state != 0 && type == 0) {
599 anomalies.push_back({
600 static_cast<uint32_t>(ALTTPMemoryMap::kSpriteType + i),
601 "type_mismatch",
602 absl::StrFormat("Sprite %d is active but has type 0", i),
603 3,
604 });
605 }
606 }
607
608 return anomalies;
609}
610
611std::vector<MemoryAnomaly> MemoryCheckTool::CheckPlayerState(
612 const std::vector<uint8_t>& wram) const {
613 std::vector<MemoryAnomaly> anomalies;
614
615 if (wram.size() < 0x100)
616 return anomalies;
617
618 uint16_t x = (wram[0x23] << 8) | wram[0x22];
619 uint16_t y = (wram[0x21] << 8) | wram[0x20];
620
621 // Check for out-of-bounds position
622 if (x > 0x2000 || y > 0x2000) {
623 anomalies.push_back({
625 "out_of_bounds",
626 absl::StrFormat("Link position (%d, %d) seems out of bounds", x, y),
627 4,
628 });
629 }
630
631 return anomalies;
632}
633
634std::vector<MemoryAnomaly> MemoryCheckTool::CheckGameMode(
635 const std::vector<uint8_t>& wram) const {
636 std::vector<MemoryAnomaly> anomalies;
637
638 if (wram.size() < 0x20)
639 return anomalies;
640
641 uint8_t mode = wram[0x10];
642
643 // Check for invalid game mode
644 if (mode > 0x1B) {
645 anomalies.push_back({
647 "invalid_mode",
648 absl::StrFormat("Invalid game mode $%02X", mode),
649 5,
650 });
651 }
652
653 return anomalies;
654}
655
656// ============================================================================
657// MemoryRegionsTool Implementation
658// ============================================================================
659
661 const resources::ArgumentParser& parser) {
662 return absl::OkStatus(); // No required args
663}
664
666 const resources::ArgumentParser& parser,
667 resources::OutputFormatter& formatter) {
668 std::string filter = parser.GetString("filter").value_or("");
669
670 auto regions = GetKnownRegions();
671
672 // Filter regions if requested
673 if (!filter.empty()) {
674 std::vector<MemoryRegionInfo> filtered;
675 for (const auto& region : regions) {
676 if (region.name.find(filter) != std::string::npos ||
677 region.description.find(filter) != std::string::npos) {
678 filtered.push_back(region);
679 }
680 }
681 regions = filtered;
682 }
683
684 // Build output as object with regions array
685 formatter.BeginObject();
686 formatter.BeginArray("regions");
687 for (const auto& region : regions) {
688 formatter.BeginObject();
689 formatter.AddField("name", region.name);
690 formatter.AddField("start", absl::StrFormat("$%06X", region.start_address));
691 formatter.AddField("end", absl::StrFormat("$%06X", region.end_address));
692 formatter.AddField("type", region.data_type);
693 formatter.AddField("description", region.description);
694 formatter.EndObject();
695 }
696 formatter.EndArray();
697 formatter.EndObject();
698 return absl::OkStatus();
699}
700
701} // namespace tools
702} // namespace agent
703} // namespace cli
704} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
std::map< std::string, std::string > AnalyzePlayerState(const std::vector< uint8_t > &wram) const
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::map< std::string, std::string > AnalyzeSpriteEntry(int sprite_index, const std::vector< uint8_t > &wram) const
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
std::map< std::string, std::string > AnalyzeGameMode(const std::vector< uint8_t > &wram) const
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::vector< MemoryAnomaly > CheckPlayerState(const std::vector< uint8_t > &wram) const
std::vector< MemoryAnomaly > CheckSpriteTable(const std::vector< uint8_t > &wram) const
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
std::vector< MemoryAnomaly > CheckGameMode(const std::vector< uint8_t > &wram) const
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
std::vector< MemoryRegionInfo > GetKnownRegions() const
Get known memory regions.
std::string IdentifyDataType(uint32_t address) const
Identify data type at address.
std::string DescribeAddress(uint32_t address) const
Get description for a memory address.
std::string FormatAscii(const std::vector< uint8_t > &data) const
Format bytes as ASCII (printable chars only)
std::string FormatHex(const std::vector< uint8_t > &data, int bytes_per_line=16) const
Format bytes as hex string.
absl::StatusOr< uint32_t > ParseAddress(const std::string &addr_str) const
Parse address from string (supports hex with $ or 0x prefix)
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::StatusOr< std::pair< std::vector< uint8_t >, std::vector< bool > > > ParsePattern(const std::string &pattern_str) const
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::vector< PatternMatch > FindMatches(const std::vector< uint8_t > &memory, uint32_t base_address, const std::vector< uint8_t > &pattern, const std::vector< bool > &mask, int max_results) const
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.