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,
93 size_t min_size = std::min(target.size(), baseline.size());
96 if (region.start >= min_size) {
100 uint32_t end = std::min(region.end,
static_cast<uint32_t
>(min_size));
101 size_t diff_count = 0;
103 for (uint32_t i = region.start; i < end; ++i) {
110 if (target[i] != baseline[i]) {
115 if (diff_count > 0) {
117 diff.
start = region.start;
133 const std::string& prefix,
136 formatter.
AddField(prefix +
"_size",
static_cast<int>(info.
size));
143void OutputTextBanner(
bool is_json) {
148 std::cout <<
"╔═══════════════════════════════════════════════════════════════╗\n";
149 std::cout <<
"║ ROM COMPARE ║\n";
150 std::cout <<
"║ Baseline Comparison Tool ║\n";
151 std::cout <<
"╚═══════════════════════════════════════════════════════════════╝\n";
155 std::cout <<
"\n=== ROM Information ===\n";
156 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"",
"Baseline",
"Target");
157 std::cout << std::string(80,
'-') <<
"\n";
158 std::cout << absl::StrFormat(
"%-20s %-30zu %-30zu\n",
"Size (bytes)",
160 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"ZSCustom Version",
163 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"Expanded Tile16",
166 std::cout << absl::StrFormat(
"%-20s %-30s %-30s\n",
"Expanded Tile32",
169 std::cout << absl::StrFormat(
"%-20s 0x%08X%21s 0x%08X\n",
"Checksum",
175 std::cout <<
"\n=== Difference Summary ===\n";
178 std::cout <<
"No differences found in critical regions.\n";
182 std::cout << absl::StrFormat(
"Found differences in %zu regions (%zu bytes total):\n",
186 std::string marker = diff.critical ?
"[CRITICAL] " :
"";
187 std::cout << absl::StrFormat(
" %s%-25s 0x%06X-0x%06X %6zu bytes differ\n",
188 marker, diff.region_name, diff.start, diff.end,
194 const std::vector<uint8_t>& baseline,
197 std::cout << absl::StrFormat(
"\n Differences in %s (0x%06X-0x%06X):\n",
200 int samples_shown = 0;
201 for (uint32_t i = region.
start;
202 i < region.
end && samples_shown < max_samples; ++i) {
203 if (target[i] != baseline[i]) {
204 std::cout << absl::StrFormat(
" 0x%06X: baseline=0x%02X target=0x%02X\n",
205 i, baseline[i], target[i]);
210 if (region.
diff_count >
static_cast<size_t>(max_samples)) {
211 std::cout << absl::StrFormat(
" ... and %zu more differences\n",
218 std::cout <<
"╔═══════════════════════════════════════════════════════════════╗\n";
219 std::cout <<
"║ ASSESSMENT ║\n";
220 std::cout <<
"╠═══════════════════════════════════════════════════════════════╣\n";
222 bool has_issues =
false;
225 std::cout <<
"║ SIZE MISMATCH: ROMs have different sizes ║\n";
231 std::cout << absl::StrFormat(
232 "║ WARNING: %s modified (%zu bytes)%-14s ║\n",
233 diff.region_name, diff.diff_count,
"");
239 std::cout <<
"║ ROMs are identical in all critical regions ║\n";
240 }
else if (!has_issues) {
241 std::cout <<
"║ ROMs have expected differences (version upgrade, etc.) ║\n";
244 std::cout <<
"╚═══════════════════════════════════════════════════════════════╝\n";
252 auto baseline_path = parser.
GetString(
"baseline");
253 bool verbose = parser.
HasFlag(
"verbose");
254 bool show_diff = parser.
HasFlag(
"show-diff");
255 bool smart_diff = parser.
HasFlag(
"smart");
256 bool is_json = formatter.
IsJson();
258 if (!baseline_path.has_value()) {
259 return absl::InvalidArgumentError(
260 "Missing required --baseline <path> argument.\n"
261 "Usage: rom-compare --rom <target> --baseline <baseline.sfc>");
264 OutputTextBanner(is_json);
268 std::cout <<
"Loading baseline ROM: " << baseline_path.value() <<
"\n";
271 std::ifstream baseline_file(baseline_path.value(), std::ios::binary);
272 if (!baseline_file) {
273 return absl::NotFoundError(
274 absl::StrFormat(
"Cannot open baseline ROM: %s", baseline_path.value()));
277 std::vector<uint8_t> baseline_data(
278 (std::istreambuf_iterator<char>(baseline_file)),
279 std::istreambuf_iterator<char>());
280 baseline_file.close();
283 const std::vector<uint8_t>& target_data = rom->
vector();
287 result.
baseline = AnalyzeRom(baseline_data, baseline_path.value());
298 FindDiffRegions(target_data, baseline_data, result, smart_diff);
301 OutputRomInfoJson(formatter,
"baseline", result.
baseline);
302 OutputRomInfoJson(formatter,
"target", result.
target);
310 std::string json = absl::StrFormat(
311 R
"({"name":"%s","start":"0x%06X","end":"0x%06X","diff_count":%zu,"critical":%s})",
312 diff.region_name, diff.start, diff.end, diff.diff_count,
313 diff.critical ? "true" :
"false");
319 bool has_critical =
false;
326 formatter.
AddField(
"has_critical_differences", has_critical);
330 :
"expected_differences"));
334 OutputTextRomInfo(result);
335 OutputTextDiffSummary(result);
338 std::cout <<
"\n=== Detailed Differences ===\n";
340 OutputTextDetailedDiff(target_data, baseline_data, diff, verbose ? 10 : 5);
344 OutputTextAssessment(result);
347 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