5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
24uint32_t
GetGfxAddress(
const uint8_t* data, uint8_t sheet_id,
size_t rom_size) {
27 if (ptr_base + 0x200 + sheet_id >= rom_size) {
31 uint8_t bank = data[ptr_base + sheet_id];
32 uint8_t high = data[ptr_base + 0x100 + sheet_id];
33 uint8_t low = data[ptr_base + 0x200 + sheet_id];
36 uint32_t snes_addr = (bank << 16) | (high << 8) | low;
39 uint32_t pc_addr = ((bank & 0x7F) * 0x8000) + (snes_addr & 0x7FFF);
46 std::vector<uint32_t>& valid_addresses) {
47 const auto& data = rom->
vector();
50 if (ptr_base + 0x300 >= rom->
size()) {
52 finding.
id =
"gfx_ptr_table_missing";
54 finding.
message =
"Graphics pointer table beyond ROM bounds";
55 finding.
location = absl::StrFormat(
"0x%06X", ptr_base);
61 int invalid_count = 0;
65 if (addr == 0 || addr >= rom->
size()) {
66 if (invalid_count < 10) {
68 finding.
id =
"invalid_gfx_ptr";
70 finding.
message = absl::StrFormat(
71 "Sheet %d has invalid pointer 0x%06X (ROM size: 0x%zX)", i, addr,
73 finding.
location = absl::StrFormat(
"Sheet %d", i);
78 valid_addresses.push_back(0);
80 valid_addresses.push_back(addr);
84 if (invalid_count > 0) {
86 finding.
id =
"gfx_ptr_summary";
89 absl::StrFormat(
"Found %d sheets with invalid pointers", invalid_count);
90 finding.
location =
"Graphics Pointer Table";
99 int& successful_decomp,
int& failed_decomp) {
100 const auto& data = rom->
vector();
103 if (i >= addresses.size() || addresses[i] == 0) {
108 uint32_t addr = addresses[i];
115 if (verbose || failed_decomp < 10) {
117 finding.
id =
"decompression_failed";
120 absl::StrFormat(
"Sheet %d decompression failed at 0x%06X: %s", i,
121 addr, std::string(result.status().message()));
122 finding.
location = absl::StrFormat(
"Sheet %d", i);
130 if (verbose || failed_decomp < 10) {
132 finding.
id =
"unexpected_sheet_size";
134 finding.
message = absl::StrFormat(
135 "Sheet %d decompressed to %zu bytes (expected %d)", i,
137 finding.
location = absl::StrFormat(
"Sheet %d", i);
149 const auto& data = rom->
vector();
153 uint32_t main_blockset_ptr = 0x5B57;
158 int invalid_refs = 0;
162 uint32_t room_blockset_ptr = 0x50C0;
166 for (
int slot = 0; slot < 4; ++slot) {
167 uint32_t addr = room_blockset_ptr + (i * 4) + slot;
168 if (addr >= rom->
size())
171 uint8_t sheet_id = data[addr];
173 if (invalid_refs < 20) {
175 finding.
id =
"invalid_blockset_ref";
177 finding.
message = absl::StrFormat(
178 "Room blockset %d slot %d references invalid sheet %d", i, slot,
180 finding.
location = absl::StrFormat(
"Room blockset %d", i);
190 if (invalid_refs > 0) {
192 finding.
id =
"blockset_summary";
194 finding.
message = absl::StrFormat(
195 "Found %d invalid blockset sheet references", invalid_refs);
196 finding.
location =
"Blockset Tables";
205 int& empty_sheets,
int& suspicious_sheets) {
206 const auto& data = rom->
vector();
209 if (i >= addresses.size() || addresses[i] == 0) {
213 uint32_t addr = addresses[i];
222 const auto& sheet_data = *result;
225 bool all_zero =
true;
227 for (uint8_t
byte : sheet_data) {
232 if (!all_zero && !all_ff)
237 if (verbose || empty_sheets < 10) {
239 finding.
id =
"empty_sheet";
241 finding.
message = absl::StrFormat(
"Sheet %d is all zeros (empty)", i);
242 finding.
location = absl::StrFormat(
"Sheet %d at 0x%06X", i, addr);
248 if (verbose || suspicious_sheets < 10) {
250 finding.
id =
"erased_sheet";
253 absl::StrFormat(
"Sheet %d is all 0xFF (erased/uninitialized)", i);
254 finding.
location = absl::StrFormat(
"Sheet %d at 0x%06X", i, addr);
269 bool verbose = parser.
HasFlag(
"verbose");
270 bool scan_all = parser.
HasFlag(
"all");
275 return absl::InvalidArgumentError(
"ROM not loaded");
279 auto sheet_arg = parser.
GetInt(
"sheet");
280 bool single_sheet = sheet_arg.ok();
281 int target_sheet = single_sheet ? *sheet_arg : -1;
284 if (target_sheet < 0 || target_sheet >=
static_cast<int>(
kNumGfxSheets)) {
285 return absl::InvalidArgumentError(absl::StrFormat(
286 "Sheet ID %d out of range (0-%d)", target_sheet,
kNumGfxSheets - 1));
291 std::vector<uint32_t> valid_addresses;
292 ValidateGraphicsPointerTable(rom, report, valid_addresses);
295 int successful_decomp = 0;
296 int failed_decomp = 0;
300 if (target_sheet <
static_cast<int>(valid_addresses.size()) &&
301 valid_addresses[target_sheet] != 0) {
302 const auto& data = rom->
vector();
307 successful_decomp = 1;
308 formatter.
AddField(
"decompressed_size",
309 static_cast<int>(result->size()));
314 }
else if (scan_all || !single_sheet) {
315 ValidateCompression(rom, valid_addresses, report, verbose,
316 successful_decomp, failed_decomp);
320 ValidateBlocksets(rom, report);
323 int empty_sheets = 0;
324 int suspicious_sheets = 0;
325 CheckSheetIntegrity(rom, valid_addresses, report, verbose, empty_sheets,
330 formatter.
AddField(
"successful_decompressions", successful_decomp);
331 formatter.
AddField(
"failed_decompressions", failed_decomp);
332 formatter.
AddField(
"empty_sheets", empty_sheets);
333 formatter.
AddField(
"suspicious_sheets", suspicious_sheets);
343 for (
const auto& finding : report.
findings) {
350 if (!formatter.
IsJson()) {
352 std::cout <<
"╔════════════════════════════════════════════════════════════"
354 std::cout <<
"║ GRAPHICS DOCTOR "
356 std::cout <<
"╠════════════════════════════════════════════════════════════"
358 std::cout << absl::StrFormat(
"║ Total Sheets: %-46d ║\n",
360 std::cout << absl::StrFormat(
"║ Successful Decompressions: %-33d ║\n",
362 std::cout << absl::StrFormat(
"║ Failed Decompressions: %-37d ║\n",
364 std::cout << absl::StrFormat(
"║ Empty Sheets: %-46d ║\n", empty_sheets);
365 std::cout << absl::StrFormat(
"║ Suspicious Sheets: %-41d ║\n",
367 std::cout <<
"╠════════════════════════════════════════════════════════════"
369 std::cout << absl::StrFormat(
370 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
373 std::cout <<
"╚════════════════════════════════════════════════════════════"
376 if (verbose && !report.
findings.empty()) {
377 std::cout <<
"\n=== Detailed Findings ===\n";
378 for (
const auto& finding : report.
findings) {
379 std::cout <<
" " << finding.FormatText() <<
"\n";
382 std::cout <<
"\nUse --verbose to see detailed findings.\n";
386 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
391 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)
uint32_t GetGfxAddress(const uint8_t *data, uint8_t sheet_id, size_t rom_size)
void ValidateBlocksets(Rom *rom, DiagnosticReport &report)
void ValidateGraphicsPointerTable(Rom *rom, DiagnosticReport &report, std::vector< uint32_t > &valid_addresses)
void CheckSheetIntegrity(Rom *rom, const std::vector< uint32_t > &addresses, DiagnosticReport &report, bool verbose, int &empty_sheets, int &suspicious_sheets)
void ValidateCompression(Rom *rom, const std::vector< uint32_t > &addresses, DiagnosticReport &report, bool verbose, int &successful_decomp, int &failed_decomp)
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kGfxGroupsPointer
constexpr uint32_t kUncompressedSheetSize
constexpr uint32_t kNumMainBlocksets
constexpr uint32_t kNumRoomBlocksets
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.