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