yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_doctor_commands.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <iostream>
5#include <map>
6#include <memory>
7#include <optional>
8#include <vector>
9
10#include "absl/status/status.h"
11#include "absl/strings/str_format.h"
13#include "core/asar_wrapper.h"
14#include "rom/rom.h"
15#include "util/platform_paths.h"
21
22namespace yaze::cli {
23
24namespace {
25
26// =============================================================================
27// Address Conversion Helpers
28// =============================================================================
29
30uint32_t SnesToPc(uint32_t snes_addr) {
31 return ((snes_addr & 0x7F0000) >> 1) | (snes_addr & 0x7FFF);
32}
33
34// =============================================================================
35// Feature Detection
36// =============================================================================
37
39 RomFeatures features;
40
41 // Detect ZSCustomOverworld version
42 if (kZSCustomVersionPos < rom->size()) {
44 features.is_vanilla = (features.zs_custom_version == 0xFF ||
45 features.zs_custom_version == 0x00);
46 features.is_v2 = (!features.is_vanilla && features.zs_custom_version == 2);
47 features.is_v3 = (!features.is_vanilla && features.zs_custom_version >= 3);
48 } else {
49 features.is_vanilla = true;
50 }
51
52 // Detect expanded tile16/tile32 (only if ASM applied)
53 if (!features.is_vanilla) {
54 if (kMap16ExpandedFlagPos < rom->size()) {
55 uint8_t flag = rom->data()[kMap16ExpandedFlagPos];
56 features.has_expanded_tile16 = (flag != 0x0F);
57 }
58
59 if (kMap32ExpandedFlagPos < rom->size()) {
60 uint8_t flag = rom->data()[kMap32ExpandedFlagPos];
61 features.has_expanded_tile32 = (flag != 0x04);
62 }
63 }
64
65 // Detect expanded pointer tables via ASM marker
66 if (kExpandedPtrTableMarker < rom->size()) {
69 }
70
71 // Detect ZSCustomOverworld feature enables
72 if (!features.is_vanilla) {
73 if (kCustomBGEnabledPos < rom->size()) {
74 features.custom_bg_enabled = (rom->data()[kCustomBGEnabledPos] != 0);
75 }
76 if (kCustomMainPalettePos < rom->size()) {
78 (rom->data()[kCustomMainPalettePos] != 0);
79 }
80 if (kCustomMosaicPos < rom->size()) {
81 features.custom_mosaic_enabled = (rom->data()[kCustomMosaicPos] != 0);
82 }
83 if (kCustomAnimatedGFXPos < rom->size()) {
85 (rom->data()[kCustomAnimatedGFXPos] != 0);
86 }
87 if (kCustomOverlayPos < rom->size()) {
88 features.custom_overlay_enabled = (rom->data()[kCustomOverlayPos] != 0);
89 }
90 if (kCustomTileGFXPos < rom->size()) {
91 features.custom_tile_gfx_enabled = (rom->data()[kCustomTileGFXPos] != 0);
92 }
93 }
94
95 return features;
96}
97
98// =============================================================================
99// Map Pointer Validation
100// =============================================================================
101
103 report.map_status.lw_dw_maps_valid = true;
104 report.map_status.sw_maps_valid = true;
105
106 for (int map_id = 0; map_id < kVanillaMapCount; ++map_id) {
107 uint32_t ptr_low_addr = kPtrTableLowBase + (3 * map_id);
108 uint32_t ptr_high_addr = kPtrTableHighBase + (3 * map_id);
109
110 if (ptr_low_addr + 3 > rom->size() || ptr_high_addr + 3 > rom->size()) {
112 if (map_id < 0x80) {
113 report.map_status.lw_dw_maps_valid = false;
114 } else {
115 report.map_status.sw_maps_valid = false;
116 }
117 continue;
118 }
119
120 uint32_t snes_low = rom->data()[ptr_low_addr] |
121 (rom->data()[ptr_low_addr + 1] << 8) |
122 (rom->data()[ptr_low_addr + 2] << 16);
123 uint32_t snes_high = rom->data()[ptr_high_addr] |
124 (rom->data()[ptr_high_addr + 1] << 8) |
125 (rom->data()[ptr_high_addr + 2] << 16);
126
127 uint32_t pc_low = SnesToPc(snes_low);
128 uint32_t pc_high = SnesToPc(snes_high);
129
130 bool low_valid = (pc_low > 0 && pc_low < rom->size());
131 bool high_valid = (pc_high > 0 && pc_high < rom->size());
132
133 if (!low_valid || !high_valid) {
135 if (map_id < 0x80) {
136 report.map_status.lw_dw_maps_valid = false;
137 } else {
138 report.map_status.sw_maps_valid = false;
139 }
140
141 DiagnosticFinding finding;
142 finding.id = "invalid_map_pointer";
144 finding.message =
145 absl::StrFormat("Map 0x%02X has invalid pointer", map_id);
146 finding.location = absl::StrFormat("0x%06X", ptr_low_addr);
147 finding.suggested_action = "Restore from baseline ROM";
148 finding.fixable = false;
149 report.AddFinding(finding);
150 }
151 }
152
153 // Tail maps status
158
159 // Add finding if map pointer corruption detected
160 if (!report.map_status.lw_dw_maps_valid) {
161 DiagnosticFinding finding;
162 finding.id = "lw_dw_corruption";
164 finding.message = "Light/Dark World map pointers are corrupted";
165 finding.location = absl::StrFormat("0x%06X-0x%06X", kPtrTableLowBase,
166 kPtrTableLowBase + 0x180);
167 finding.suggested_action =
168 "ROM may be severely damaged. Restore from backup.";
169 finding.fixable = false;
170 report.AddFinding(finding);
171 }
172
173 if (!report.map_status.sw_maps_valid) {
174 DiagnosticFinding finding;
175 finding.id = "sw_corruption";
177 finding.message = "Special World map pointers are corrupted";
178 finding.location = absl::StrFormat(
179 "0x%06X-0x%06X", kPtrTableLowBase + 0x180, kPtrTableHighBase);
180 finding.suggested_action = "Restore Special World data from baseline";
181 finding.fixable = false;
182 report.AddFinding(finding);
183 }
184}
185
186// =============================================================================
187// Tile16 Corruption Check
188// =============================================================================
189
191 // Report the layout only. A zeroed or odd-looking entry can be a real edit,
192 // and an earlier heuristic here zeroed valid Oracle of Secrets tiles.
194}
195
196// =============================================================================
197// Baseline ROM Loading
198// =============================================================================
199
200std::unique_ptr<Rom> LoadBaselineRom(const std::optional<std::string>& path,
201 std::string* resolved_path) {
202 std::vector<std::string> candidates;
203 if (path.has_value()) {
204 candidates.push_back(*path);
205 } else {
206 candidates = {"alttp_vanilla.sfc", "vanilla.sfc", "zelda3.sfc"};
207 }
208
209 for (const auto& candidate : candidates) {
210 std::ifstream probe(candidate, std::ios::binary);
211 if (!probe.good())
212 continue;
213 probe.close();
214
215 auto baseline = std::make_unique<Rom>();
216 auto status = baseline->LoadFromFile(candidate);
217 if (status.ok()) {
218 if (resolved_path)
219 *resolved_path = candidate;
220 return baseline;
221 }
222 }
223
224 return nullptr;
225}
226
227// =============================================================================
228// Distribution Stats for Entity Coverage
229// =============================================================================
230
231template <typename T, typename Getter>
232MapDistributionStats BuildDistribution(const std::vector<T>& entries,
233 Getter getter) {
235 for (const auto& entry : entries) {
236 uint16_t map = getter(entry);
237 stats.counts[map]++;
238 stats.total++;
239 if (map >= zelda3::kNumOverworldMaps) {
240 stats.invalid++;
241 }
242 }
243 stats.unique = static_cast<int>(stats.counts.size());
244
245 for (const auto& [map, count] : stats.counts) {
246 if (count > stats.most_common_count) {
247 stats.most_common_count = count;
248 stats.most_common_map = map;
249 }
250 }
251 return stats;
252}
253
254absl::StatusOr<std::vector<zelda3::OverworldMap>> BuildOverworldMaps(Rom* rom) {
255 std::vector<zelda3::OverworldMap> maps;
256 maps.reserve(zelda3::kNumOverworldMaps);
257 for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
258 maps.emplace_back(i, rom);
259 }
260 return maps;
261}
262
263// =============================================================================
264// Repair Functions
265// =============================================================================
266
267// Apply tail map expansion ASM patch
268absl::Status ApplyTailExpansion(Rom* rom, bool dry_run, bool verbose) {
269 // Check if already applied
270 if (kExpandedPtrTableMarker < rom->size() &&
272 return absl::AlreadyExistsError(
273 "Tail map expansion already applied (marker 0xEA found at 0x1423FF)");
274 }
275
276 // Check if ZSCustomOverworld v3 is present (required prerequisite)
277 if (kZSCustomVersionPos < rom->size()) {
278 uint8_t version = rom->data()[kZSCustomVersionPos];
279 if (version < 3 && version != 0xFF && version != 0x00) {
280 return absl::FailedPreconditionError(
281 "Tail map expansion requires ZSCustomOverworld v3 or later. "
282 "Apply ZSCustomOverworld v3 first.");
283 }
284 }
285
286 // Refuse when the compatibility check (overworld-allocation --tail-check)
287 // says the patch would break this ROM.
288 if (auto guard = zelda3::TailMapExpansionApplyGuard(*rom); !guard.ok()) {
289 return guard;
290 }
291
292 if (dry_run) {
293 return absl::OkStatus();
294 }
295
296 auto patch_path =
297 util::PlatformPaths::FindAsset("patches/Overworld/TailMapExpansion.asm");
298 if (!patch_path.ok()) {
299 return absl::NotFoundError(
300 "TailMapExpansion.asm patch file not found. "
301 "Expected it in the Yaze runtime assets.");
302 }
303
304 // Apply the patch using Asar
307
308 std::vector<uint8_t> rom_data(rom->data(), rom->data() + rom->size());
309 auto result = asar.ApplyPatch(patch_path->string(), rom_data);
310
311 if (!result.ok()) {
312 return result.status();
313 }
314
315 if (!result->success) {
316 std::string error_msg = "Asar patch failed:";
317 for (const auto& err : result->errors) {
318 error_msg += " " + err;
319 }
320 return absl::InternalError(error_msg);
321 }
322
323 // Handle ROM size changes - patches may expand the ROM for custom code
324 if (rom_data.size() > rom->size()) {
325 if (verbose) {
326 std::cout << absl::StrFormat(" Expanding ROM from %zu to %zu bytes\n",
327 rom->size(), rom_data.size());
328 }
329 rom->Expand(static_cast<int>(rom_data.size()));
330 } else if (rom_data.size() < rom->size()) {
331 // ROM shrinking is unexpected and likely an error
332 return absl::InternalError(
333 absl::StrFormat("ROM size decreased unexpectedly: %zu -> %zu",
334 rom->size(), rom_data.size()));
335 }
336
337 // Copy patched data back to ROM
338 for (size_t i = 0; i < rom_data.size(); ++i) {
339 (*rom)[i] = rom_data[i];
340 }
341
342 // Verify marker was written (with bounds check to prevent buffer overflow)
343 if (kExpandedPtrTableMarker >= rom->size()) {
344 return absl::InternalError(
345 absl::StrFormat("ROM too small for expansion marker at 0x%06X "
346 "(ROM size: 0x%06zX). Patch may have failed.",
348 }
350 return absl::InternalError(
351 "Patch applied but marker not found. Patch may be incomplete.");
352 }
353
354 return absl::OkStatus();
355}
356
357// =============================================================================
358// Output Helpers
359// =============================================================================
360
362 const RomFeatures& features) {
363 formatter.AddField("zs_custom_version", features.GetVersionString());
364 formatter.AddField("is_vanilla", features.is_vanilla);
365 formatter.AddField("expanded_tile16", features.has_expanded_tile16);
366 formatter.AddField("expanded_tile32", features.has_expanded_tile32);
367 formatter.AddField("expanded_pointer_tables",
369
370 if (!features.is_vanilla) {
371 formatter.AddField("custom_bg_enabled", features.custom_bg_enabled);
372 formatter.AddField("custom_main_palette_enabled",
374 formatter.AddField("custom_mosaic_enabled", features.custom_mosaic_enabled);
375 formatter.AddField("custom_animated_gfx_enabled",
377 formatter.AddField("custom_overlay_enabled",
378 features.custom_overlay_enabled);
379 formatter.AddField("custom_tile_gfx_enabled",
380 features.custom_tile_gfx_enabled);
381 }
382}
383
385 const MapPointerStatus& status) {
386 formatter.AddField("lw_dw_maps_valid", status.lw_dw_maps_valid);
387 formatter.AddField("sw_maps_valid", status.sw_maps_valid);
388 formatter.AddField("tail_maps_available", status.can_support_tail);
389 formatter.AddField("invalid_map_count", status.invalid_map_count);
390}
391
393 const DiagnosticReport& report) {
394 formatter.BeginArray("findings");
395 for (const auto& finding : report.findings) {
396 formatter.AddArrayItem(finding.FormatJson());
397 }
398 formatter.EndArray();
399}
400
402 const DiagnosticReport& report) {
403 formatter.AddField("total_findings", report.TotalFindings());
404 formatter.AddField("critical_count", report.critical_count);
405 formatter.AddField("error_count", report.error_count);
406 formatter.AddField("warning_count", report.warning_count);
407 formatter.AddField("info_count", report.info_count);
408 formatter.AddField("fixable_count", report.fixable_count);
409 formatter.AddField("has_problems", report.HasProblems());
410}
411
412void OutputTextBanner(bool is_json) {
413 if (is_json)
414 return;
415 std::cout << "\n";
416 std::cout
417 << "╔═══════════════════════════════════════════════════════════════╗\n";
418 std::cout
419 << "║ OVERWORLD DOCTOR ║\n";
420 std::cout
421 << "║ ROM Diagnostic & Repair Tool ║\n";
422 std::cout
423 << "╚═══════════════════════════════════════════════════════════════╝\n";
424}
425
426void OutputTextSummary(const DiagnosticReport& report) {
427 std::cout << "\n";
428 std::cout
429 << "╔═══════════════════════════════════════════════════════════════╗\n";
430 std::cout
431 << "║ DIAGNOSTIC SUMMARY ║\n";
432 std::cout
433 << "╠═══════════════════════════════════════════════════════════════╣\n";
434
435 std::cout << absl::StrFormat("║ ROM Version: %-46s ║\n",
436 report.features.GetVersionString());
437
438 std::cout << absl::StrFormat(
439 "║ Expanded Tile16: %-42s ║\n",
440 report.features.has_expanded_tile16 ? "YES" : "NO");
441 std::cout << absl::StrFormat(
442 "║ Expanded Tile32: %-42s ║\n",
443 report.features.has_expanded_tile32 ? "YES" : "NO");
444 std::cout << absl::StrFormat("║ Expanded Ptr Tables: %-38s ║\n",
446 ? "YES (192 maps)"
447 : "NO (160 maps)");
448
449 std::cout
450 << "╠═══════════════════════════════════════════════════════════════╣\n";
451
452 std::cout << absl::StrFormat(
453 "║ Light/Dark World (0x00-0x7F): %-29s ║\n",
454 report.map_status.lw_dw_maps_valid ? "OK" : "CORRUPTED");
455 std::cout << absl::StrFormat(
456 "║ Special World (0x80-0x9F): %-32s ║\n",
457 report.map_status.sw_maps_valid ? "OK" : "CORRUPTED");
458 std::cout << absl::StrFormat("║ Tail Maps (0xA0-0xBF): %-36s ║\n",
460 ? "Available"
461 : "N/A (no ASM expansion)");
462
463 if (report.tile16_status.uses_expanded) {
464 std::cout << "╠════════════════════════════════════════════════════════════"
465 "═══╣\n";
466 std::cout << "║ Tile16 Data: not checked (compare with a backup) "
467 " ║\n";
468 }
469
470 std::cout
471 << "╠═══════════════════════════════════════════════════════════════╣\n";
472 std::cout << absl::StrFormat("║ Total Findings: %-43d ║\n",
473 report.TotalFindings());
474 std::cout << absl::StrFormat(
475 "║ Critical: %-3d Errors: %-3d Warnings: %-3d Info: %-3d%-4s ║\n",
476 report.critical_count, report.error_count, report.warning_count,
477 report.info_count, "");
478 std::cout << absl::StrFormat("║ Fixable Issues: %-43d ║\n",
479 report.fixable_count);
480 std::cout
481 << "╚═══════════════════════════════════════════════════════════════╝\n";
482}
483
485 if (report.findings.empty()) {
486 return;
487 }
488
489 std::cout << "\n=== Detailed Findings ===\n";
490 for (const auto& finding : report.findings) {
491 std::cout << " " << finding.FormatText() << "\n";
492 if (!finding.suggested_action.empty()) {
493 std::cout << " → " << finding.suggested_action << "\n";
494 }
495 }
496}
497
498} // namespace
499
501 Rom* rom, const resources::ArgumentParser& parser,
502 resources::OutputFormatter& formatter) {
503 bool fix_mode = parser.HasFlag("fix");
504 bool apply_tail_expansion = parser.HasFlag("apply-tail-expansion");
505 bool dry_run = parser.HasFlag("dry-run");
506 bool verbose = parser.HasFlag("verbose");
507 auto output_path = parser.GetString("output");
508 auto baseline_path = parser.GetString("baseline");
509 bool is_json = formatter.IsJson();
510
511 // Show text banner for text mode
512 OutputTextBanner(is_json);
513
514 // Build diagnostic report
515 DiagnosticReport report;
516 report.rom_path = rom->filename();
517 report.features = DetectRomFeatures(rom);
518 ValidateMapPointers(rom, report);
519 CheckTile16Region(report);
520
521 // Load baseline if provided
522 std::string resolved_baseline;
523 auto baseline_rom = LoadBaselineRom(baseline_path, &resolved_baseline);
524
525 // Add info finding if no ASM expansion for tail maps
527 DiagnosticFinding finding;
528 finding.id = "no_tail_support";
530 finding.message = "Tail maps (0xA0-0xBF) not available";
531 finding.location = "";
532 finding.suggested_action =
533 "Apply TailMapExpansion.asm patch (after ZSCustomOverworld v3) to "
534 "expand pointer tables to 192 entries. Use: z3ed overworld-doctor "
535 "--apply-tail-expansion or apply manually with Asar.";
536 finding.fixable = false;
537 report.AddFinding(finding);
538 }
539
540 // Output to formatter
541 formatter.AddField("rom_path", report.rom_path);
542 formatter.AddField("fix_mode", fix_mode);
543 formatter.AddField("dry_run", dry_run);
544
545 // Features section
546 if (is_json) {
547 OutputFeaturesJson(formatter, report.features);
548 OutputMapStatusJson(formatter, report.map_status);
549 OutputFindingsJson(formatter, report);
550 OutputSummaryJson(formatter, report);
551 }
552
553 // Text mode: show nice ASCII summary
554 if (!is_json) {
555 OutputTextSummary(report);
556 if (verbose) {
557 OutputTextFindings(report);
558 }
559 }
560
561 // Entity coverage (text mode only for now)
562 if (!is_json) {
563 ASSIGN_OR_RETURN(auto exits, zelda3::LoadExits(rom));
564 ASSIGN_OR_RETURN(auto entrances, zelda3::LoadEntrances(rom));
565 ASSIGN_OR_RETURN(auto maps, BuildOverworldMaps(rom));
566 ASSIGN_OR_RETURN(auto items, zelda3::LoadItems(rom, maps));
567
568 auto exit_stats =
569 BuildDistribution(exits, [](const auto& exit) { return exit.map_id_; });
570 auto entrance_stats = BuildDistribution(entrances, [](const auto& ent) {
571 return static_cast<uint16_t>(ent.map_id_);
572 });
573 auto item_stats =
574 BuildDistribution(items, [](const auto& item) { return item.map_id_; });
575
576 std::cout << "\n=== Overworld Entity Coverage ===\n";
577 std::cout << absl::StrFormat(
578 " exits : total=%d unique=%d most_common=0x%02X (%d)\n",
579 exit_stats.total, exit_stats.unique, exit_stats.most_common_map,
580 exit_stats.most_common_count);
581 std::cout << absl::StrFormat(
582 " entrances : total=%d unique=%d most_common=0x%02X (%d)\n",
583 entrance_stats.total, entrance_stats.unique,
584 entrance_stats.most_common_map, entrance_stats.most_common_count);
585 std::cout << absl::StrFormat(
586 " items : total=%d unique=%d most_common=0x%02X (%d)\n",
587 item_stats.total, item_stats.unique, item_stats.most_common_map,
588 item_stats.most_common_count);
589
590 if (baseline_rom) {
591 std::cout << absl::StrFormat(" Baseline used: %s\n", resolved_baseline);
592 }
593 }
594
595 // Apply tail expansion if requested
596 if (apply_tail_expansion) {
597 if (dry_run) {
598 if (!is_json) {
599 std::cout << "\n=== Dry Run - Tail Map Expansion ===\n";
601 std::cout << " Tail expansion already applied.\n";
602 } else {
603 std::cout << " Would apply TailMapExpansion.asm patch.\n";
604 std::cout << " This will:\n";
605 std::cout << " - Relocate pointer tables to $28:A400\n";
606 std::cout << " - Expand from 160 to 192 map entries\n";
607 std::cout << " - Write marker byte 0xEA at $28:A3FF\n";
608 std::cout << " - Add blank map data at $30:8000\n";
609 if (auto guard = zelda3::TailMapExpansionApplyGuard(*rom);
610 !guard.ok()) {
611 std::cout << " A real run would refuse:\n"
612 << guard.message() << "\n";
613 }
614 }
615 std::cout << "\nNo changes made (dry run).\n";
616 }
617 formatter.AddField("dry_run_tail_expansion", true);
618 } else {
619 auto status = ApplyTailExpansion(rom, false, verbose);
620 if (status.ok()) {
621 if (!is_json) {
622 std::cout << "\n=== Tail Map Expansion Applied ===\n";
623 std::cout << " Pointer tables relocated to $28:A400/$28:A640\n";
624 std::cout << " Maps 0xA0-0xBF now available for editing\n";
625 }
626 formatter.AddField("tail_expansion_applied", true);
627
628 // Re-detect features after patch
629 report.features = DetectRomFeatures(rom);
630 } else if (absl::IsAlreadyExists(status)) {
631 if (!is_json) {
632 std::cout << "\n[INFO] Tail expansion already applied.\n";
633 }
634 formatter.AddField("tail_expansion_already_applied", true);
635 } else {
636 if (!is_json) {
637 std::cout << "\n[ERROR] Failed to apply tail expansion: "
638 << status.message() << "\n";
639 }
640 formatter.AddField("tail_expansion_error",
641 std::string(status.message()));
642 if (absl::IsFailedPrecondition(status)) {
643 return status; // Refused: fail the command.
644 }
645 // Continue with diagnostics, don't fail the whole command
646 }
647 }
648 }
649
650 // Fix mode handling
651 if (fix_mode) {
652 if (dry_run) {
653 if (!is_json) {
654 std::cout << "\n=== Dry Run - Planned Fixes ===\n";
655 std::cout << " No fixes needed.\n";
656 std::cout << "\nNo changes made (dry run).\n";
657 }
658 formatter.AddField("dry_run_fixes_planned", 0);
659 } else {
660 // Save if output path provided
661 if (output_path.has_value()) {
662 Rom::SaveSettings settings;
663 settings.filename = output_path.value();
664 RETURN_IF_ERROR(rom->SaveToFile(settings));
665 if (!is_json) {
666 std::cout << absl::StrFormat("\nSaved fixed ROM to: %s\n",
667 output_path.value());
668 }
669 formatter.AddField("output_file", output_path.value());
670 } else if (report.HasFixable()) {
671 if (!is_json) {
672 std::cout
673 << "\nNo output path specified. Use --output <path> to save.\n";
674 }
675 }
676 }
677 } else {
678 // Not in fix mode - show hint
679 if (!is_json && report.HasFixable()) {
680 std::cout << "\nTo apply available fixes, run with --fix flag.\n";
681 std::cout << "To preview fixes, use --fix --dry-run.\n";
682 std::cout << "To save to a new file, use --output <path>.\n";
683 }
684 }
685
686 return absl::OkStatus();
687}
688
689} // namespace yaze::cli
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto filename() const
Definition rom.h:207
void Expand(int size)
Definition rom.h:92
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:648
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
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)
bool HasFlag(const std::string &name) const
Check if a flag is present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
bool IsJson() const
Check if using JSON format.
Modern C++ wrapper for Asar 65816 assembler integration.
absl::StatusOr< AsarPatchResult > ApplyPatch(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths={})
absl::Status Initialize()
static absl::StatusOr< std::filesystem::path > FindAsset(const std::string &relative_path)
Find an asset file in multiple standard locations.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::unique_ptr< Rom > LoadBaselineRom(const std::optional< std::string > &path, std::string *resolved_path)
void OutputFeaturesJson(resources::OutputFormatter &formatter, const RomFeatures &features)
absl::Status ApplyTailExpansion(Rom *rom, bool dry_run, bool verbose)
void OutputSummaryJson(resources::OutputFormatter &formatter, const DiagnosticReport &report)
void OutputMapStatusJson(resources::OutputFormatter &formatter, const MapPointerStatus &status)
void OutputFindingsJson(resources::OutputFormatter &formatter, const DiagnosticReport &report)
absl::StatusOr< std::vector< zelda3::OverworldMap > > BuildOverworldMaps(Rom *rom)
MapDistributionStats BuildDistribution(const std::vector< T > &entries, Getter getter)
constexpr uint32_t kCustomBGEnabledPos
constexpr uint32_t kExpandedPtrTableMarker
constexpr uint32_t kPtrTableHighBase
constexpr uint32_t kCustomOverlayPos
constexpr uint8_t kExpandedPtrTableMagic
constexpr uint32_t kMap32ExpandedFlagPos
constexpr uint32_t kZSCustomVersionPos
constexpr uint32_t kMap16ExpandedFlagPos
constexpr uint32_t kPtrTableLowBase
constexpr uint32_t kCustomMosaicPos
constexpr uint32_t kCustomTileGFXPos
constexpr int kVanillaMapCount
constexpr uint32_t kCustomAnimatedGFXPos
constexpr uint32_t kCustomMainPalettePos
absl::StatusOr< std::vector< OverworldEntrance > > LoadEntrances(Rom *rom)
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int kNumOverworldMaps
Definition common.h:85
absl::Status TailMapExpansionApplyGuard(const Rom &rom)
absl::StatusOr< std::vector< OverworldExit > > LoadExits(Rom *rom)
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
Read-only map of overworld area IDs $00-$BF: which IDs are used, which per-area tables cover each ID,...
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::string filename
Definition rom.h:54
A single diagnostic finding.
Complete diagnostic report.
std::vector< DiagnosticFinding > findings
int TotalFindings() const
Get total finding count.
bool HasProblems() const
Check if report has any critical or error findings.
void AddFinding(const DiagnosticFinding &finding)
Add a finding and update counts.
bool HasFixable() const
Check if report has any fixable findings.
Entity distribution statistics for coverage analysis.
std::map< uint16_t, int > counts
Map pointer validation status.
ROM feature detection results.
std::string GetVersionString() const
Get version as human-readable string.