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)",
72 i, addr, rom->
size());
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";
88 finding.
message = absl::StrFormat(
89 "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];
116 if (verbose || failed_decomp < 10) {
118 finding.
id =
"decompression_failed";
120 finding.
message = absl::StrFormat(
121 "Sheet %d decompression failed at 0x%06X: %s",
122 i, addr, std::string(result.status().message()));
123 finding.
location = absl::StrFormat(
"Sheet %d", i);
131 if (verbose || failed_decomp < 10) {
133 finding.
id =
"unexpected_sheet_size";
135 finding.
message = absl::StrFormat(
136 "Sheet %d decompressed to %zu bytes (expected %d)",
138 finding.
location = absl::StrFormat(
"Sheet %d", i);
150 const auto& data = rom->
vector();
154 uint32_t main_blockset_ptr = 0x5B57;
159 int invalid_refs = 0;
163 uint32_t room_blockset_ptr = 0x50C0;
167 for (
int slot = 0; slot < 4; ++slot) {
168 uint32_t addr = room_blockset_ptr + (i * 4) + slot;
169 if (addr >= rom->
size())
break;
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",
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];
220 if (!result.ok())
continue;
222 const auto& sheet_data = *result;
225 bool all_zero =
true;
227 for (uint8_t
byte : sheet_data) {
228 if (
byte != 0x00) all_zero =
false;
229 if (
byte != 0xFF) all_ff =
false;
230 if (!all_zero && !all_ff)
break;
234 if (verbose || empty_sheets < 10) {
236 finding.
id =
"empty_sheet";
238 finding.
message = absl::StrFormat(
239 "Sheet %d is all zeros (empty)", i);
240 finding.
location = absl::StrFormat(
"Sheet %d at 0x%06X", i, addr);
246 if (verbose || suspicious_sheets < 10) {
248 finding.
id =
"erased_sheet";
250 finding.
message = absl::StrFormat(
251 "Sheet %d is all 0xFF (erased/uninitialized)", i);
252 finding.
location = absl::StrFormat(
"Sheet %d at 0x%06X", i, addr);
267 bool verbose = parser.
HasFlag(
"verbose");
268 bool scan_all = parser.
HasFlag(
"all");
273 return absl::InvalidArgumentError(
"ROM not loaded");
277 auto sheet_arg = parser.
GetInt(
"sheet");
278 bool single_sheet = sheet_arg.ok();
279 int target_sheet = single_sheet ? *sheet_arg : -1;
282 if (target_sheet < 0 || target_sheet >=
static_cast<int>(
kNumGfxSheets)) {
283 return absl::InvalidArgumentError(absl::StrFormat(
284 "Sheet ID %d out of range (0-%d)", target_sheet,
kNumGfxSheets - 1));
289 std::vector<uint32_t> valid_addresses;
290 ValidateGraphicsPointerTable(rom, report, valid_addresses);
293 int successful_decomp = 0;
294 int failed_decomp = 0;
298 if (target_sheet <
static_cast<int>(valid_addresses.size()) &&
299 valid_addresses[target_sheet] != 0) {
300 const auto& data = rom->
vector();
302 data.data(), valid_addresses[target_sheet],
305 successful_decomp = 1;
306 formatter.
AddField(
"decompressed_size",
307 static_cast<int>(result->size()));
312 }
else if (scan_all || !single_sheet) {
313 ValidateCompression(rom, valid_addresses, report, verbose,
314 successful_decomp, failed_decomp);
318 ValidateBlocksets(rom, report);
321 int empty_sheets = 0;
322 int suspicious_sheets = 0;
323 CheckSheetIntegrity(rom, valid_addresses, report, verbose,
324 empty_sheets, suspicious_sheets);
328 formatter.
AddField(
"successful_decompressions", successful_decomp);
329 formatter.
AddField(
"failed_decompressions", failed_decomp);
330 formatter.
AddField(
"empty_sheets", empty_sheets);
331 formatter.
AddField(
"suspicious_sheets", suspicious_sheets);
341 for (
const auto& finding : report.
findings) {
348 if (!formatter.
IsJson()) {
350 std::cout <<
"╔═══════════════════════════════════════════════════════════════╗\n";
351 std::cout <<
"║ GRAPHICS DOCTOR ║\n";
352 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
353 std::cout << absl::StrFormat(
"║ Total Sheets: %-46d ║\n",
355 std::cout << absl::StrFormat(
"║ Successful Decompressions: %-33d ║\n",
357 std::cout << absl::StrFormat(
"║ Failed Decompressions: %-37d ║\n",
359 std::cout << absl::StrFormat(
"║ Empty Sheets: %-46d ║\n", empty_sheets);
360 std::cout << absl::StrFormat(
"║ Suspicious Sheets: %-41d ║\n",
362 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
363 std::cout << absl::StrFormat(
364 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
367 std::cout <<
"╚═══════════════════════════════════════════════════════════════╝\n";
369 if (verbose && !report.
findings.empty()) {
370 std::cout <<
"\n=== Detailed Findings ===\n";
371 for (
const auto& finding : report.
findings) {
372 std::cout <<
" " << finding.FormatText() <<
"\n";
375 std::cout <<
"\nUse --verbose to see detailed findings.\n";
379 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
384 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.