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) {
105 if (!smart_diff)
return false;
110 if (i >= 0x140141 && i <= 0x140148)
118 bool in_diff =
false;
119 size_t diff_count = 0;
121 for (uint32_t i = 0; i < min_size; ++i) {
122 if (is_ignored(i))
continue;
124 if (target[i] != baseline[i]) {
131 }
else if (in_diff) {
147 if (!region_filter.empty() && region.category != region_filter) {
151 if (region.start >= min_size) {
155 uint32_t end = std::min(region.end,
static_cast<uint32_t
>(min_size));
156 size_t diff_count = 0;
158 for (uint32_t i = region.start; i < end; ++i) {
159 if (is_ignored(i))
continue;
161 if (target[i] != baseline[i]) {
166 if (diff_count > 0) {
168 diff.
start = region.start;
184 const std::string& prefix,
187 formatter.
AddField(prefix +
"_size",
static_cast<int>(info.
size));
194void OutputTextBanner(
bool is_json) {
200 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
202 <<
"║ ROM COMPARE ║\n";
204 <<
"║ Baseline Comparison Tool ║\n";
206 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
210 std::cout <<
"\n=== ROM Information ===\n";
211 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"",
"Baseline",
"Target");
212 std::cout << std::string(80,
'-') <<
"\n";
213 std::cout << absl::StrFormat(
"%-20s %-30zu %-30zu\n",
"Size (bytes)",
215 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"ZSCustom Version",
218 std::cout << absl::StrFormat(
219 "%-20s %-30s %-30s\n",
"Expanded Tile16",
222 std::cout << absl::StrFormat(
223 "%-20s %-30s %-30s\n",
"Expanded Tile32",
226 std::cout << absl::StrFormat(
"%-20s 0x%08X%21s 0x%08X\n",
"Checksum",
232 std::cout <<
"\n=== Difference Summary ===\n";
235 std::cout <<
"No differences found in specified regions.\n";
239 std::cout << absl::StrFormat(
240 "Found differences in %zu regions (%zu bytes total):\n",
244 std::string marker = diff.critical ?
"[CRITICAL] " :
"";
245 std::cout << absl::StrFormat(
" %s%-25s 0x%06X-0x%06X %6zu bytes differ\n",
246 marker, diff.region_name, diff.start, diff.end,
252 const std::vector<uint8_t>& baseline,
255 std::cout << absl::StrFormat(
"\n Differences in %s (0x%06X-0x%06X):\n",
258 int samples_shown = 0;
259 for (uint32_t i = region.
start; i < region.
end && samples_shown < max_samples;
261 if (target[i] != baseline[i]) {
262 std::cout << absl::StrFormat(
263 " 0x%06X: base 0x%02X | target 0x%02X\n", i, baseline[i],
269 if (region.
diff_count >
static_cast<size_t>(max_samples)) {
270 std::cout << absl::StrFormat(
" ... and %zu more differences\n",
278 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
280 <<
"║ ASSESSMENT ║\n";
282 <<
"╠═══════════════════════════════════════════════════════════════╣\n";
284 bool has_issues =
false;
288 <<
"║ SIZE MISMATCH: ROMs have different sizes ║\n";
294 std::cout << absl::StrFormat(
295 "║ WARNING: %s modified (%zu bytes)%-14s ║\n", diff.region_name,
296 diff.diff_count,
"");
303 <<
"║ ROMs are identical in all checked regions ║\n";
304 }
else if (!has_issues) {
306 <<
"║ ROMs have expected differences (modifications detected) ║\n";
310 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
318 auto baseline_path = parser.
GetString(
"baseline");
319 bool verbose = parser.
HasFlag(
"verbose");
320 bool show_diff = parser.
HasFlag(
"show-diff");
321 bool smart_diff = parser.
HasFlag(
"smart");
322 bool scan_all = parser.
HasFlag(
"all");
323 std::string region_filter = parser.
GetString(
"region").value_or(
"");
324 bool is_json = formatter.
IsJson();
326 if (!baseline_path.has_value()) {
327 return absl::InvalidArgumentError(
328 "Missing required --baseline <path> argument.\n"
329 "Usage: rom-compare --rom <target> --baseline <baseline.sfc>");
332 OutputTextBanner(is_json);
336 std::cout <<
"Loading baseline ROM: " << baseline_path.value() <<
"\n";
339 std::ifstream baseline_file(baseline_path.value(), std::ios::binary);
340 if (!baseline_file) {
341 return absl::NotFoundError(
342 absl::StrFormat(
"Cannot open baseline ROM: %s", baseline_path.value()));
345 std::vector<uint8_t> baseline_data(
346 (std::istreambuf_iterator<char>(baseline_file)),
347 std::istreambuf_iterator<char>());
348 baseline_file.close();
351 const std::vector<uint8_t>& target_data = rom->
vector();
355 result.
baseline = AnalyzeRom(baseline_data, baseline_path.value());
367 FindDiffRegions(target_data, baseline_data, result, smart_diff, region_filter, scan_all);
370 OutputRomInfoJson(formatter,
"baseline", result.
baseline);
371 OutputRomInfoJson(formatter,
"target", result.
target);
375 formatter.
AddField(
"total_diff_bytes",
380 std::string json = absl::StrFormat(
381 R
"({"name":"%s","start":"0x%06X","end":"0x%06X","diff_count":%zu,"critical":%s})",
382 diff.region_name, diff.start, diff.end, diff.diff_count,
383 diff.critical ? "true" :
"false");
389 bool has_critical =
false;
396 formatter.
AddField(
"has_critical_differences", has_critical);
397 formatter.
AddField(
"assessment", has_critical
401 :
"expected_differences"));
405 OutputTextRomInfo(result);
406 OutputTextDiffSummary(result);
409 std::cout <<
"\n=== Detailed Differences ===\n";
411 OutputTextDetailedDiff(target_data, baseline_data, diff,
416 OutputTextAssessment(result);
419 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)
Namespace for the command line interface.
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