9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
30 {
"Map32 Ptr Low", 0x1794D, 0x17B2D,
true},
31 {
"Map32 Ptr High", 0x17B2D, 0x17D0D,
true},
32 {
"Tile16 Vanilla", 0x78000, 0x78000 + (3752 * 8),
false},
33 {
"Tile16 Expanded", 0x1E8000, 0x1F0000,
false},
34 {
"Tile32 BL Expanded", 0x1F0000, 0x1F8000,
false},
35 {
"Tile32 BR Expanded", 0x1F8000, 0x200000,
false},
36 {
"ZSCustom Tables", 0x140000, 0x142000,
false},
37 {
"Overlay Space", 0x120000, 0x130000,
false},
46 for (
size_t i = 0; i < data.size(); ++i) {
57 const std::string& name) {
60 info.
size = data.size();
83 if (version == 0xFF || version == 0x00) {
86 return absl::StrFormat(
"v%d", version);
90 const std::vector<uint8_t>& baseline,
92 size_t min_size = std::min(target.size(), baseline.size());
95 if (region.start >= min_size) {
99 uint32_t end = std::min(region.end,
static_cast<uint32_t
>(min_size));
100 size_t diff_count = 0;
102 for (uint32_t i = region.start; i < end; ++i) {
111 if (target[i] != baseline[i]) {
116 if (diff_count > 0) {
118 diff.
start = region.start;
134 const std::string& prefix,
137 formatter.
AddField(prefix +
"_size",
static_cast<int>(info.
size));
144void OutputTextBanner(
bool is_json) {
150 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
152 <<
"║ ROM COMPARE ║\n";
154 <<
"║ Baseline Comparison Tool ║\n";
156 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
160 std::cout <<
"\n=== ROM Information ===\n";
161 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"",
"Baseline",
"Target");
162 std::cout << std::string(80,
'-') <<
"\n";
163 std::cout << absl::StrFormat(
"%-20s %-30zu %-30zu\n",
"Size (bytes)",
165 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"ZSCustom Version",
168 std::cout << absl::StrFormat(
169 "%-20s %-30s %-30s\n",
"Expanded Tile16",
172 std::cout << absl::StrFormat(
173 "%-20s %-30s %-30s\n",
"Expanded Tile32",
176 std::cout << absl::StrFormat(
"%-20s 0x%08X%21s 0x%08X\n",
"Checksum",
182 std::cout <<
"\n=== Difference Summary ===\n";
185 std::cout <<
"No differences found in critical regions.\n";
189 std::cout << absl::StrFormat(
190 "Found differences in %zu regions (%zu bytes total):\n",
194 std::string marker = diff.critical ?
"[CRITICAL] " :
"";
195 std::cout << absl::StrFormat(
" %s%-25s 0x%06X-0x%06X %6zu bytes differ\n",
196 marker, diff.region_name, diff.start, diff.end,
202 const std::vector<uint8_t>& baseline,
205 std::cout << absl::StrFormat(
"\n Differences in %s (0x%06X-0x%06X):\n",
208 int samples_shown = 0;
209 for (uint32_t i = region.
start; i < region.
end && samples_shown < max_samples;
211 if (target[i] != baseline[i]) {
212 std::cout << absl::StrFormat(
213 " 0x%06X: baseline=0x%02X target=0x%02X\n", i, baseline[i],
219 if (region.
diff_count >
static_cast<size_t>(max_samples)) {
220 std::cout << absl::StrFormat(
" ... and %zu more differences\n",
228 <<
"╔═══════════════════════════════════════════════════════════════╗\n";
230 <<
"║ ASSESSMENT ║\n";
232 <<
"╠═══════════════════════════════════════════════════════════════╣\n";
234 bool has_issues =
false;
238 <<
"║ SIZE MISMATCH: ROMs have different sizes ║\n";
244 std::cout << absl::StrFormat(
245 "║ WARNING: %s modified (%zu bytes)%-14s ║\n", diff.region_name,
246 diff.diff_count,
"");
253 <<
"║ ROMs are identical in all critical regions ║\n";
254 }
else if (!has_issues) {
256 <<
"║ ROMs have expected differences (version upgrade, etc.) ║\n";
260 <<
"╚═══════════════════════════════════════════════════════════════╝\n";
268 auto baseline_path = parser.
GetString(
"baseline");
269 bool verbose = parser.
HasFlag(
"verbose");
270 bool show_diff = parser.
HasFlag(
"show-diff");
271 bool smart_diff = parser.
HasFlag(
"smart");
272 bool is_json = formatter.
IsJson();
274 if (!baseline_path.has_value()) {
275 return absl::InvalidArgumentError(
276 "Missing required --baseline <path> argument.\n"
277 "Usage: rom-compare --rom <target> --baseline <baseline.sfc>");
280 OutputTextBanner(is_json);
284 std::cout <<
"Loading baseline ROM: " << baseline_path.value() <<
"\n";
287 std::ifstream baseline_file(baseline_path.value(), std::ios::binary);
288 if (!baseline_file) {
289 return absl::NotFoundError(
290 absl::StrFormat(
"Cannot open baseline ROM: %s", baseline_path.value()));
293 std::vector<uint8_t> baseline_data(
294 (std::istreambuf_iterator<char>(baseline_file)),
295 std::istreambuf_iterator<char>());
296 baseline_file.close();
299 const std::vector<uint8_t>& target_data = rom->
vector();
303 result.
baseline = AnalyzeRom(baseline_data, baseline_path.value());
315 FindDiffRegions(target_data, baseline_data, result, smart_diff);
318 OutputRomInfoJson(formatter,
"baseline", result.
baseline);
319 OutputRomInfoJson(formatter,
"target", result.
target);
323 formatter.
AddField(
"total_diff_bytes",
328 std::string json = absl::StrFormat(
329 R
"({"name":"%s","start":"0x%06X","end":"0x%06X","diff_count":%zu,"critical":%s})",
330 diff.region_name, diff.start, diff.end, diff.diff_count,
331 diff.critical ? "true" :
"false");
337 bool has_critical =
false;
344 formatter.
AddField(
"has_critical_differences", has_critical);
345 formatter.
AddField(
"assessment", has_critical
349 :
"expected_differences"));
353 OutputTextRomInfo(result);
354 OutputTextDiffSummary(result);
357 std::cout <<
"\n=== Detailed Differences ===\n";
359 OutputTextDetailedDiff(target_data, baseline_data, diff,
364 OutputTextAssessment(result);
367 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.
void FindDiffRegions(const std::vector< uint8_t > &target, const std::vector< uint8_t > &baseline, RomCompareResult &result, bool smart_diff)
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 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