5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
19 for (
size_t i = 0; i < msg.
Data.size(); ++i) {
20 uint8_t
byte = msg.
Data[i];
22 if (
byte >= 0x67 &&
byte <= 0x80) {
24 if (!cmd.has_value()) {
26 finding.
id =
"unknown_control_code";
28 finding.
message = absl::StrFormat(
29 "Unknown control code 0x%02X in message %d",
byte, msg.
ID);
30 finding.
location = absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
33 }
else if (cmd->HasArgument) {
35 if (i + 1 >= msg.
Data.size()) {
37 finding.
id =
"missing_command_arg";
39 finding.
message = absl::StrFormat(
40 "Control code [%s] missing argument in message %d", cmd->Token,
43 absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
58 if (msg.
Data.empty()) {
60 finding.
id =
"empty_message";
62 finding.
message = absl::StrFormat(
"Message %d is empty", msg.
ID);
63 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
72 finding.
id =
"missing_terminator";
75 absl::StrFormat(
"Message %d missing 0x7F terminator (ends with 0x%02X)",
77 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
86 const std::vector<editor::DictionaryEntry>& dict,
88 for (
size_t i = 0; i < msg.
Data.size(); ++i) {
89 uint8_t
byte = msg.
Data[i];
92 if (dict_idx >=
static_cast<int>(dict.size())) {
94 finding.
id =
"invalid_dict_ref";
96 finding.
message = absl::StrFormat(
97 "Invalid dictionary reference [D:%02X] in message %d (max valid: "
99 dict_idx, msg.
ID,
static_cast<int>(dict.size()) - 1);
100 finding.
location = absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
114 const int kCorruptionThreshold = 10;
116 for (uint8_t
byte : msg.
Data) {
120 }
else if (
byte == 0xFF) {
128 if (zero_run >= kCorruptionThreshold) {
130 finding.
id =
"possible_corruption_zeros";
132 finding.
message = absl::StrFormat(
133 "Large block of zeros (%d bytes) in message %d", zero_run, msg.
ID);
134 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
141 if (ff_run >= kCorruptionThreshold) {
143 finding.
id =
"possible_corruption_ff";
145 finding.
message = absl::StrFormat(
146 "Large block of 0xFF (%d bytes) in message %d", ff_run, msg.
ID);
147 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
161 bool verbose = args.
HasFlag(
"verbose");
166 return absl::InvalidArgumentError(
"ROM not loaded");
170 std::vector<editor::DictionaryEntry> dictionary;
173 }
catch (
const std::exception& e) {
175 finding.
id =
"dictionary_build_failed";
178 absl::StrFormat(
"Failed to build dictionary: %s", e.what());
179 finding.
location =
"Dictionary Tables";
186 return absl::OkStatus();
190 std::vector<editor::MessageData> messages;
192 uint8_t* mutable_data =
const_cast<uint8_t*
>(rom->
data());
195 }
catch (
const std::exception& e) {
197 finding.
id =
"message_scan_failed";
199 finding.
message = absl::StrFormat(
"Failed to scan messages: %s", e.what());
200 finding.
location =
"Message Data Region";
209 for (
const auto& msg : messages) {
210 if (msg.Data.empty()) {
216 ValidateControlCodes(msg, report);
217 ValidateTerminators(msg, report);
218 ValidateDictionaryRefs(msg, dictionary, report);
219 CheckCorruptionPatterns(msg, report);
225 formatter.
AddField(
"messages_scanned",
static_cast<int>(messages.size()));
226 formatter.
AddField(
"valid_messages", valid_count);
227 formatter.
AddField(
"empty_messages", empty_count);
228 formatter.
AddField(
"dictionary_entries",
static_cast<int>(dictionary.size()));
239 for (
const auto& finding : report.
findings) {
246 if (!formatter.
IsJson()) {
248 std::cout <<
"╔════════════════════════════════════════════════════════════"
250 std::cout <<
"║ MESSAGE DOCTOR "
252 std::cout <<
"╠════════════════════════════════════════════════════════════"
254 std::cout << absl::StrFormat(
"║ Messages Scanned: %-42d ║\n",
255 static_cast<int>(messages.size()));
256 std::cout << absl::StrFormat(
"║ Valid Messages: %-44d ║\n", valid_count);
257 std::cout << absl::StrFormat(
"║ Empty Messages: %-44d ║\n", empty_count);
258 std::cout << absl::StrFormat(
"║ Dictionary Entries: %-40d ║\n",
259 static_cast<int>(dictionary.size()));
260 std::cout <<
"╠════════════════════════════════════════════════════════════"
262 std::cout << absl::StrFormat(
263 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
267 std::cout << absl::StrFormat(
"║ Fixable Issues: %-44d ║\n",
270 std::cout <<
"╚════════════════════════════════════════════════════════════"
273 if (verbose && !report.
findings.empty()) {
274 std::cout <<
"\n=== Detailed Findings ===\n";
275 for (
const auto& finding : report.
findings) {
276 std::cout <<
" " << finding.FormatText() <<
"\n";
279 std::cout <<
"\nUse --verbose to see detailed findings.\n";
283 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
288 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status Execute(Rom *rom, const resources::ArgumentParser &args, 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.
void ValidateDictionaryRefs(const editor::MessageData &msg, const std::vector< editor::DictionaryEntry > &dict, DiagnosticReport &report)
void CheckCorruptionPatterns(const editor::MessageData &msg, DiagnosticReport &report)
void ValidateControlCodes(const editor::MessageData &msg, DiagnosticReport &report)
void ValidateTerminators(const editor::MessageData &msg, DiagnosticReport &report)
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos)
constexpr uint8_t kMessageTerminator
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
constexpr uint8_t DICTOFF
std::optional< TextElement > FindMatchingCommand(uint8_t b)
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.
std::vector< uint8_t > Data