5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
20 const auto& data = rom->
vector();
23 int invalid_count = 0;
28 if (ptr_addr + 1 >= rom->
size()) {
30 finding.
id =
"sprite_ptr_out_of_bounds";
32 finding.
message = absl::StrFormat(
33 "Sprite pointer table address 0x%06X is beyond ROM size", ptr_addr);
34 finding.
location = absl::StrFormat(
"Room %d", room);
41 uint16_t ptr = data[ptr_addr] | (data[ptr_addr + 1] << 8);
44 uint32_t sprite_addr = 0x090000 + ptr;
49 if (verbose || invalid_count < 10) {
51 finding.
id =
"invalid_sprite_ptr";
53 finding.
message = absl::StrFormat(
54 "Room %d sprite pointer 0x%04X -> 0x%06X outside valid range "
58 finding.
location = absl::StrFormat(
"0x%06X", ptr_addr);
66 if (invalid_count > 0) {
68 finding.
id =
"sprite_ptr_summary";
70 finding.
message = absl::StrFormat(
71 "Found %d rooms with potentially invalid sprite pointers", invalid_count);
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)",
116 absl::StrFormat(
"Spriteset %d slot %d", set, slot);
125 if (invalid_refs > 0) {
127 finding.
id =
"spriteset_summary";
129 finding.
message = absl::StrFormat(
130 "Found %d invalid spriteset sheet references", invalid_refs);
131 finding.
location =
"Spriteset Table";
139 int& total_sprites,
int& empty_rooms) {
140 const auto& data = rom->
vector();
144 if (ptr_addr + 1 >= rom->
size())
return;
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())
return;
158 if (sprite_addr >= rom->
size())
return;
163 int room_sprite_count = 0;
164 const int kMaxSpritesPerRoom = 32;
166 while (sprite_addr < rom->size() && room_sprite_count < kMaxSpritesPerRoom) {
167 uint8_t y_pos = data[sprite_addr];
168 if (y_pos == 0xFF)
break;
170 if (sprite_addr + 2 >= rom->
size()) {
172 finding.
id =
"truncated_sprite_data";
174 finding.
message = absl::StrFormat(
175 "Room %d sprite data truncated at 0x%06X", room_id, sprite_addr);
176 finding.
location = absl::StrFormat(
"Room %d", room_id);
183 uint8_t sprite_id = data[sprite_addr + 2];
188 if (sprite_id > 0xF2) {
199 if (room_sprite_count >= kMaxSpritesPerRoom) {
201 finding.
id =
"sprite_count_limit";
203 finding.
message = absl::StrFormat(
204 "Room %d has %d+ sprites (hit scan limit)", room_id, kMaxSpritesPerRoom);
205 finding.
location = absl::StrFormat(
"Room %d", room_id);
213 const auto& data = rom->
vector();
216 int zero_pointers = 0;
219 if (ptr_addr + 1 >= rom->
size())
break;
221 uint16_t ptr = data[ptr_addr] | (data[ptr_addr + 1] << 8);
227 if (zero_pointers > 50) {
229 finding.
id =
"many_zero_sprite_ptrs";
231 finding.
message = absl::StrFormat(
232 "Found %d rooms with zero sprite pointers (possible corruption)",
234 finding.
location =
"Sprite Pointer Table";
246 bool verbose = parser.
HasFlag(
"verbose");
247 bool scan_all = parser.
HasFlag(
"all");
252 return absl::InvalidArgumentError(
"ROM not loaded");
256 auto room_arg = parser.
GetInt(
"room");
257 bool single_room = room_arg.ok();
258 int target_room = single_room ? *room_arg : -1;
262 return absl::InvalidArgumentError(absl::StrFormat(
263 "Room ID %d out of range (0-%d)", target_room,
269 ValidateSpritePointerTable(rom, report, verbose);
272 ValidateSpritesets(rom, report);
275 int total_sprites = 0;
277 int rooms_scanned = 0;
280 ValidateRoomSprites(rom, target_room, report, total_sprites, empty_rooms);
282 }
else if (scan_all) {
284 ValidateRoomSprites(rom, room, report, total_sprites, empty_rooms);
289 std::vector<int> sample_rooms;
290 for (
int i = 0; i < 20; ++i) sample_rooms.push_back(i);
291 for (
int i = 100; i < 110; ++i) sample_rooms.push_back(i);
292 for (
int i = 200; i < 210; ++i) sample_rooms.push_back(i);
294 for (
int room : sample_rooms) {
296 ValidateRoomSprites(rom, room, report, total_sprites, empty_rooms);
303 CheckCommonSpriteIssues(rom, report);
306 formatter.
AddField(
"rooms_scanned", rooms_scanned);
307 formatter.
AddField(
"total_sprites", total_sprites);
308 formatter.
AddField(
"empty_rooms", empty_rooms);
318 for (
const auto& finding : report.
findings) {
325 if (!formatter.
IsJson()) {
327 std::cout <<
"╔═══════════════════════════════════════════════════════════════╗\n";
328 std::cout <<
"║ SPRITE DOCTOR ║\n";
329 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
330 std::cout << absl::StrFormat(
"║ Rooms Scanned: %-45d ║\n", rooms_scanned);
331 std::cout << absl::StrFormat(
"║ Total Sprites Found: %-39d ║\n",
333 std::cout << absl::StrFormat(
"║ Empty Rooms: %-47d ║\n", empty_rooms);
334 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
335 std::cout << absl::StrFormat(
336 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
339 std::cout <<
"╚═══════════════════════════════════════════════════════════════╝\n";
341 if (verbose && !report.
findings.empty()) {
342 std::cout <<
"\n=== Detailed Findings ===\n";
343 for (
const auto& finding : report.
findings) {
344 std::cout <<
" " << finding.FormatText() <<
"\n";
347 std::cout <<
"\nUse --verbose to see detailed findings.\n";
351 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
356 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.