5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
20 const auto& data = rom->
vector();
23 int invalid_count = 0;
27 if (ptr_addr + 1 >= rom->
size()) {
29 finding.
id =
"sprite_ptr_out_of_bounds";
31 finding.
message = absl::StrFormat(
32 "Sprite pointer table address 0x%06X is beyond ROM size", ptr_addr);
33 finding.
location = absl::StrFormat(
"Room %d", room);
40 uint16_t ptr = data[ptr_addr] | (data[ptr_addr + 1] << 8);
43 uint32_t sprite_addr = 0x090000 + ptr;
48 if (verbose || invalid_count < 10) {
50 finding.
id =
"invalid_sprite_ptr";
52 finding.
message = absl::StrFormat(
53 "Room %d sprite pointer 0x%04X -> 0x%06X outside valid range "
57 finding.
location = absl::StrFormat(
"0x%06X", ptr_addr);
65 if (invalid_count > 0) {
67 finding.
id =
"sprite_ptr_summary";
69 finding.
message = absl::StrFormat(
70 "Found %d rooms with potentially invalid sprite pointers",
72 finding.
location =
"Sprite Pointer Table";
80 const auto& data = rom->
vector();
91 for (
int slot = 0; slot < 4; ++slot) {
92 uint32_t addr = spriteset_addr + (set * 4) + slot;
93 if (addr >= rom->
size()) {
95 finding.
id =
"spriteset_addr_out_of_bounds";
97 finding.
message = absl::StrFormat(
98 "Spriteset %d address 0x%06X beyond ROM size", set, addr);
99 finding.
location = absl::StrFormat(
"Spriteset %d", set);
105 uint8_t sheet_id = data[addr];
108 if (invalid_refs < 20) {
110 finding.
id =
"invalid_spriteset_sheet";
112 finding.
message = absl::StrFormat(
113 "Spriteset %d slot %d references invalid sheet %d (max: %d)", set,
115 finding.
location = absl::StrFormat(
"Spriteset %d slot %d", set, slot);
124 if (invalid_refs > 0) {
126 finding.
id =
"spriteset_summary";
128 finding.
message = absl::StrFormat(
129 "Found %d invalid spriteset sheet references", invalid_refs);
130 finding.
location =
"Spriteset Table";
138 int& total_sprites,
int& empty_rooms) {
139 const auto& data = rom->
vector();
143 if (ptr_addr + 1 >= rom->
size())
146 uint16_t ptr = data[ptr_addr] | (data[ptr_addr + 1] << 8);
147 uint32_t sprite_addr = 0x090000 + ptr;
155 if (sprite_addr >= rom->
size())
159 if (sprite_addr >= rom->
size())
165 int room_sprite_count = 0;
166 const int kMaxSpritesPerRoom = 32;
168 while (sprite_addr < rom->size() && room_sprite_count < kMaxSpritesPerRoom) {
169 uint8_t y_pos = data[sprite_addr];
173 if (sprite_addr + 2 >= rom->
size()) {
175 finding.
id =
"truncated_sprite_data";
177 finding.
message = absl::StrFormat(
178 "Room %d sprite data truncated at 0x%06X", room_id, sprite_addr);
179 finding.
location = absl::StrFormat(
"Room %d", room_id);
186 uint8_t sprite_id = data[sprite_addr + 2];
191 if (sprite_id > 0xF2) {
202 if (room_sprite_count >= kMaxSpritesPerRoom) {
204 finding.
id =
"sprite_count_limit";
207 absl::StrFormat(
"Room %d has %d+ sprites (hit scan limit)", room_id,
209 finding.
location = absl::StrFormat(
"Room %d", room_id);
217 const auto& data = rom->
vector();
220 int zero_pointers = 0;
223 if (ptr_addr + 1 >= rom->
size())
226 uint16_t ptr = data[ptr_addr] | (data[ptr_addr + 1] << 8);
232 if (zero_pointers > 50) {
234 finding.
id =
"many_zero_sprite_ptrs";
236 finding.
message = absl::StrFormat(
237 "Found %d rooms with zero sprite pointers (possible corruption)",
239 finding.
location =
"Sprite Pointer Table";
251 bool verbose = parser.
HasFlag(
"verbose");
252 bool scan_all = parser.
HasFlag(
"all");
257 return absl::InvalidArgumentError(
"ROM not loaded");
261 auto room_arg = parser.
GetInt(
"room");
262 bool single_room = room_arg.ok();
263 int target_room = single_room ? *room_arg : -1;
267 return absl::InvalidArgumentError(
268 absl::StrFormat(
"Room ID %d out of range (0-%d)", target_room,
274 ValidateSpritePointerTable(rom, report, verbose);
277 ValidateSpritesets(rom, report);
280 int total_sprites = 0;
282 int rooms_scanned = 0;
285 ValidateRoomSprites(rom, target_room, report, total_sprites, empty_rooms);
287 }
else if (scan_all) {
289 ValidateRoomSprites(rom, room, report, total_sprites, empty_rooms);
294 std::vector<int> sample_rooms;
295 for (
int i = 0; i < 20; ++i)
296 sample_rooms.push_back(i);
297 for (
int i = 100; i < 110; ++i)
298 sample_rooms.push_back(i);
299 for (
int i = 200; i < 210; ++i)
300 sample_rooms.push_back(i);
302 for (
int room : sample_rooms) {
304 ValidateRoomSprites(rom, room, report, total_sprites, empty_rooms);
311 CheckCommonSpriteIssues(rom, report);
314 formatter.
AddField(
"rooms_scanned", rooms_scanned);
315 formatter.
AddField(
"total_sprites", total_sprites);
316 formatter.
AddField(
"empty_rooms", empty_rooms);
326 for (
const auto& finding : report.
findings) {
333 if (!formatter.
IsJson()) {
335 std::cout <<
"╔════════════════════════════════════════════════════════════"
337 std::cout <<
"║ SPRITE DOCTOR "
339 std::cout <<
"╠════════════════════════════════════════════════════════════"
341 std::cout << absl::StrFormat(
"║ Rooms Scanned: %-45d ║\n", rooms_scanned);
342 std::cout << absl::StrFormat(
"║ Total Sprites Found: %-39d ║\n",
344 std::cout << absl::StrFormat(
"║ Empty Rooms: %-47d ║\n", empty_rooms);
345 std::cout <<
"╠════════════════════════════════════════════════════════════"
347 std::cout << absl::StrFormat(
348 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
351 std::cout <<
"╚════════════════════════════════════════════════════════════"
354 if (verbose && !report.
findings.empty()) {
355 std::cout <<
"\n=== Detailed Findings ===\n";
356 for (
const auto& finding : report.
findings) {
357 std::cout <<
" " << finding.FormatText() <<
"\n";
360 std::cout <<
"\nUse --verbose to see detailed findings.\n";
364 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
369 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
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.
bool HasFlag(const std::string &name) const
Check if a flag is present.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
void ValidateSpritesets(Rom *rom, DiagnosticReport &report)
void ValidateRoomSprites(Rom *rom, int room_id, DiagnosticReport &report, int &total_sprites, int &empty_rooms)
void CheckCommonSpriteIssues(Rom *rom, DiagnosticReport &report)
void ValidateSpritePointerTable(Rom *rom, DiagnosticReport &report, bool verbose)
constexpr int kSpritesEndData
constexpr int kSpriteBlocksetPointer
constexpr int kSpritesData
constexpr int kRoomsSpritePointer
constexpr int kNumberOfRooms
constexpr int kSpritesDataEmptyRoom
constexpr uint32_t kNumSpritesets
constexpr uint32_t kNumGfxSheets
A single diagnostic finding.
std::string suggested_action
DiagnosticSeverity severity
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.