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";
29 absl::StrFormat(
"Unknown control code 0x%02X in message %d",
byte,
31 finding.
location = absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
34 }
else if (cmd->HasArgument) {
36 if (i + 1 >= msg.
Data.size()) {
38 finding.
id =
"missing_command_arg";
41 absl::StrFormat(
"Control code [%s] missing argument in message %d",
44 absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
59 if (msg.
Data.empty()) {
61 finding.
id =
"empty_message";
63 finding.
message = absl::StrFormat(
"Message %d is empty", msg.
ID);
64 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
73 finding.
id =
"missing_terminator";
75 finding.
message = absl::StrFormat(
76 "Message %d missing 0x7F terminator (ends with 0x%02X)", msg.
ID,
78 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
87 const std::vector<editor::DictionaryEntry>& dict,
89 for (
size_t i = 0; i < msg.
Data.size(); ++i) {
90 uint8_t
byte = msg.
Data[i];
93 if (dict_idx >=
static_cast<int>(dict.size())) {
95 finding.
id =
"invalid_dict_ref";
97 finding.
message = absl::StrFormat(
98 "Invalid dictionary reference [D:%02X] in message %d (max valid: "
100 dict_idx, msg.
ID,
static_cast<int>(dict.size()) - 1);
101 finding.
location = absl::StrFormat(
"Message %d offset %zu", msg.
ID, i);
115 const int kCorruptionThreshold = 10;
117 for (uint8_t
byte : msg.
Data) {
121 }
else if (
byte == 0xFF) {
129 if (zero_run >= kCorruptionThreshold) {
131 finding.
id =
"possible_corruption_zeros";
134 absl::StrFormat(
"Large block of zeros (%d bytes) in message %d",
136 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
143 if (ff_run >= kCorruptionThreshold) {
145 finding.
id =
"possible_corruption_ff";
148 absl::StrFormat(
"Large block of 0xFF (%d bytes) in message %d",
150 finding.
location = absl::StrFormat(
"Message %d", msg.
ID);
164 bool verbose = args.
HasFlag(
"verbose");
169 return absl::InvalidArgumentError(
"ROM not loaded");
173 std::vector<editor::DictionaryEntry> dictionary;
176 }
catch (
const std::exception& e) {
178 finding.
id =
"dictionary_build_failed";
181 absl::StrFormat(
"Failed to build dictionary: %s", e.what());
182 finding.
location =
"Dictionary Tables";
189 return absl::OkStatus();
193 std::vector<editor::MessageData> messages;
195 uint8_t* mutable_data =
const_cast<uint8_t*
>(rom->
data());
198 }
catch (
const std::exception& e) {
200 finding.
id =
"message_scan_failed";
202 finding.
message = absl::StrFormat(
"Failed to scan messages: %s", e.what());
203 finding.
location =
"Message Data Region";
212 for (
const auto& msg : messages) {
213 if (msg.Data.empty()) {
219 ValidateControlCodes(msg, report);
220 ValidateTerminators(msg, report);
221 ValidateDictionaryRefs(msg, dictionary, report);
222 CheckCorruptionPatterns(msg, report);
228 formatter.
AddField(
"messages_scanned",
static_cast<int>(messages.size()));
229 formatter.
AddField(
"valid_messages", valid_count);
230 formatter.
AddField(
"empty_messages", empty_count);
231 formatter.
AddField(
"dictionary_entries",
static_cast<int>(dictionary.size()));
242 for (
const auto& finding : report.
findings) {
249 if (!formatter.
IsJson()) {
251 std::cout <<
"╔═══════════════════════════════════════════════════════════════╗\n";
252 std::cout <<
"║ MESSAGE DOCTOR ║\n";
253 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
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 <<
"╠═══════════════════════════════════════════════════════════════╣\n";
261 std::cout << absl::StrFormat(
262 "║ Findings: %d total (%d errors, %d warnings, %d info)%-8s ║\n",
266 std::cout << absl::StrFormat(
"║ Fixable Issues: %-44d ║\n",
269 std::cout <<
"╚═══════════════════════════════════════════════════════════════╝\n";
271 if (verbose && !report.
findings.empty()) {
272 std::cout <<
"\n=== Detailed Findings ===\n";
273 for (
const auto& finding : report.
findings) {
274 std::cout <<
" " << finding.FormatText() <<
"\n";
277 std::cout <<
"\nUse --verbose to see detailed findings.\n";
281 std::cout <<
"\n \033[1;32mNo critical issues found.\033[0m\n";
286 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