9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
31 {
"Map32 Ptr Low", 0x1794D, 0x17B2D,
true,
"overworld"},
32 {
"Map32 Ptr High", 0x17B2D, 0x17D0D,
true,
"overworld"},
33 {
"Overworld Data", 0x70000, 0x78000,
true,
"overworld"},
34 {
"Tile16 Vanilla", 0x78000, 0x78000 + (3752 * 8),
false,
"overworld"},
35 {
"Tile16 Expanded", 0x1E8000, 0x1F0000,
false,
"overworld"},
36 {
"Tile32 BL Expanded", 0x1F0000, 0x1F8000,
false,
"overworld"},
37 {
"Tile32 BR Expanded", 0x1F8000, 0x200000,
false,
"overworld"},
38 {
"Dungeon Ptr Table", 0x01F800, 0x01FB00,
true,
"dungeon"},
39 {
"Dungeon Room Data", 0x1D8000, 0x1E8000,
true,
"dungeon"},
40 {
"Message Data", 0x1C0000, 0x1D8000,
false,
"text"},
41 {
"ZSCustom Tables", 0x140000, 0x142000,
false,
"system"},
42 {
"Overlay Space", 0x120000, 0x130000,
false,
"overworld"},
43 {
"SNES Header", 0x7FC0, 0x8000,
true,
"system"},
44 {
"Bank 00 Code", 0x000000, 0x008000,
true,
"code"},
45 {
"Bank 01 Code", 0x008000, 0x010000,
true,
"code"},
46 {
"Bank 02 Code", 0x010000, 0x018000,
true,
"code"},
55 for (
size_t i = 0; i < data.size(); ++i) {
66 const std::string& name) {
69 info.
size = data.size();
92 if (version == 0xFF || version == 0x00) {
95 return absl::StrFormat(
"v%d", version);
99 const std::vector<uint8_t>& baseline,
101 const std::string& region_filter,
bool scan_all) {
102 size_t min_size = std::min(target.size(), baseline.size());
104 auto is_ignored = [&](uint32_t i) {
112 if (i >= 0x140141 && i <= 0x140148)
120 bool in_diff =
false;
121 size_t diff_count = 0;
123 for (uint32_t i = 0; i < min_size; ++i) {
127 if (target[i] != baseline[i]) {
134 }
else if (in_diff) {
149 auto checked = [&](uint32_t i) {
151 if ((region_filter.empty() || region.category == region_filter) &&
152 i >= region.start && i < region.end) {
158 for (uint32_t i = 0; i < min_size; ++i) {
159 if (target[i] != baseline[i] && !is_ignored(i) && !checked(i)) {
165 if (!region_filter.empty() && region.category != region_filter) {
169 if (region.start >= min_size) {
173 uint32_t end = std::min(region.end,
static_cast<uint32_t
>(min_size));
174 size_t diff_count = 0;
176 for (uint32_t i = region.start; i < end; ++i) {
180 if (target[i] != baseline[i]) {
185 if (diff_count > 0) {
187 diff.
start = region.start;
203 const std::string& prefix,
206 formatter.
AddField(prefix +
"_size",
static_cast<int>(info.
size));
213void OutputTextBanner(
bool is_json) {
219 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
221 <<
"║ ROM COMPARE ║\n";
223 <<
"║ Baseline Comparison Tool ║\n";
225 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
229 std::cout <<
"\n=== ROM Information ===\n";
230 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"",
"Baseline",
"Target");
231 std::cout << std::string(80,
'-') <<
"\n";
232 std::cout << absl::StrFormat(
"%-20s %-30zu %-30zu\n",
"Size (bytes)",
234 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"ZSCustom Version",
237 std::cout << absl::StrFormat(
238 "%-20s %-30s %-30s\n",
"Expanded Tile16",
241 std::cout << absl::StrFormat(
242 "%-20s %-30s %-30s\n",
"Expanded Tile32",
245 std::cout << absl::StrFormat(
"%-20s 0x%08X%21s 0x%08X\n",
"Checksum",
251 std::cout <<
"\n=== Difference Summary ===\n";
254 std::cout <<
"No differences found in specified regions.\n";
258 std::cout << absl::StrFormat(
259 "Found differences in %zu regions (%zu bytes total):\n",
263 std::string marker = diff.critical ?
"[CRITICAL] " :
"";
264 std::cout << absl::StrFormat(
" %s%-25s 0x%06X-0x%06X %6zu bytes differ\n",
265 marker, diff.region_name, diff.start, diff.end,
271 const std::vector<uint8_t>& baseline,
274 std::cout << absl::StrFormat(
"\n Differences in %s (0x%06X-0x%06X):\n",
277 int samples_shown = 0;
278 for (uint32_t i = region.
start; i < region.
end && samples_shown < max_samples;
280 if (target[i] != baseline[i]) {
281 std::cout << absl::StrFormat(
282 " 0x%06X: base 0x%02X | target 0x%02X\n", i, baseline[i],
288 if (region.
diff_count >
static_cast<size_t>(max_samples)) {
289 std::cout << absl::StrFormat(
" ... and %zu more differences\n",
297 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
299 <<
"║ ASSESSMENT ║\n";
301 <<
"╠═══════════════════════════════════════════════════════════════╣\n";
303 bool has_issues =
false;
307 <<
"║ SIZE MISMATCH: ROMs have different sizes ║\n";
313 std::cout << absl::StrFormat(
314 "║ WARNING: %s modified (%zu bytes)%-14s ║\n", diff.region_name,
315 diff.diff_count,
"");
321 std::cout << absl::StrFormat(
322 "║ %-7zu byte(s) differ outside the checked regions (--all) ║\n",
328 <<
"║ ROMs are identical in all checked regions ║\n";
329 }
else if (!has_issues) {
331 <<
"║ ROMs have expected differences (modifications detected) ║\n";
335 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
343 auto baseline_path = parser.
GetString(
"baseline");
344 bool verbose = parser.
HasFlag(
"verbose");
345 bool show_diff = parser.
HasFlag(
"show-diff");
346 bool smart_diff = parser.
HasFlag(
"smart");
347 bool scan_all = parser.
HasFlag(
"all");
348 std::string region_filter = parser.
GetString(
"region").value_or(
"");
349 bool is_json = formatter.
IsJson();
351 if (!baseline_path.has_value()) {
352 return absl::InvalidArgumentError(
353 "Missing required --baseline <path> argument.\n"
354 "Usage: rom-compare --rom <target> --baseline <baseline.sfc>");
357 OutputTextBanner(is_json);
361 std::cout <<
"Loading baseline ROM: " << baseline_path.value() <<
"\n";
364 std::ifstream baseline_file(baseline_path.value(), std::ios::binary);
365 if (!baseline_file) {
366 return absl::NotFoundError(
367 absl::StrFormat(
"Cannot open baseline ROM: %s", baseline_path.value()));
370 std::vector<uint8_t> baseline_data(
371 (std::istreambuf_iterator<char>(baseline_file)),
372 std::istreambuf_iterator<char>());
373 baseline_file.close();
376 const std::vector<uint8_t>& target_data = rom->
vector();
380 result.
baseline = AnalyzeRom(baseline_data, baseline_path.value());
392 FindDiffRegions(target_data, baseline_data, result, smart_diff, region_filter,
396 OutputRomInfoJson(formatter,
"baseline", result.
baseline);
397 OutputRomInfoJson(formatter,
"target", result.
target);
401 formatter.
AddField(
"total_diff_bytes",
403 formatter.
AddField(
"unchecked_diff_bytes",
408 std::string json = absl::StrFormat(
409 R
"({"name":"%s","start":"0x%06X","end":"0x%06X","diff_count":%zu,"critical":%s})",
410 diff.region_name, diff.start, diff.end, diff.diff_count,
411 diff.critical ? "true" :
"false");
417 bool has_critical =
false;
424 formatter.
AddField(
"has_critical_differences", has_critical);
425 const char* assessment =
"expected_differences";
427 assessment =
"warning";
430 ?
"differences_outside_checked_regions"
433 formatter.
AddField(
"assessment", assessment);
437 OutputTextRomInfo(result);
438 OutputTextDiffSummary(result);
441 std::cout <<
"\n=== Detailed Differences ===\n";
443 OutputTextDetailedDiff(target_data, baseline_data, diff,
448 OutputTextAssessment(result);
451 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.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
RomCompareResult::RomInfo AnalyzeRom(const std::vector< uint8_t > &data, const std::string &name)
void OutputTextDetailedDiff(const std::vector< uint8_t > &target, const std::vector< uint8_t > &baseline, const RomCompareResult::DiffRegion ®ion, int max_samples)
void OutputTextDiffSummary(const RomCompareResult &result)
std::string GetVersionString(uint8_t version)
uint32_t CalculateChecksum(const std::vector< uint8_t > &data)
void FindDiffRegions(const std::vector< uint8_t > &target, const std::vector< uint8_t > &baseline, RomCompareResult &result, bool smart_diff, const std::string ®ion_filter, bool scan_all)
void OutputTextRomInfo(const RomCompareResult &result)
void OutputRomInfoJson(resources::OutputFormatter &formatter, const std::string &prefix, const RomCompareResult::RomInfo &info)
const RomRegion kCriticalRegions[]
void OutputTextAssessment(const RomCompareResult &result)
constexpr uint32_t kChecksumPos
constexpr uint32_t kMap32ExpandedFlagPos
constexpr uint32_t kZSCustomVersionPos
constexpr uint32_t kMap16ExpandedFlagPos
constexpr uint32_t kChecksumComplementPos
ROM comparison result for baseline comparisons.
std::vector< DiffRegion > diff_regions
size_t unchecked_diff_bytes