14#include "absl/strings/str_format.h"
30static const std::vector<MemoryRegion> kKnownRegions = {
32 {0x000000, 0x00FFFF,
"graphics",
"Compressed graphics"},
33 {0x080000, 0x08FFFF,
"graphics",
"More graphics data"},
36 {0x020000, 0x027FFF,
"overworld",
"Overworld tilemap data"},
37 {0x0A8000, 0x0AFFFF,
"overworld",
"Overworld map data"},
40 {0x028000, 0x02FFFF,
"dungeon",
"Dungeon room data"},
41 {0x0B0000, 0x0B7FFF,
"dungeon",
"Dungeon tileset data"},
44 {0x09C800, 0x09DFFF,
"sprites",
"Sprite graphics"},
45 {0x0D8000, 0x0DFFFF,
"sprites",
"More sprite data"},
48 {0x0DD218, 0x0DD4FF,
"palettes",
"Main palettes"},
49 {0x0DE000, 0x0DEFFF,
"palettes",
"Sprite palettes"},
52 {0x0E0000, 0x0E7FFF,
"messages",
"Text and messages"},
55 {0x008000, 0x00FFFF,
"code",
"Bank $00 code"},
56 {0x018000, 0x01FFFF,
"code",
"Bank $03 code"},
59 {0x0C0000, 0x0CFFFF,
"audio",
"Music/SPC data"},
69 std::string rom1_path = parser.
GetString(
"rom1").value();
70 std::string rom2_path = parser.
GetString(
"rom2").value();
71 bool semantic = parser.
HasFlag(
"semantic");
72 std::string format = parser.
GetString(
"format").value_or(
"json");
78 return absl::InvalidArgumentError(
"Failed to load ROM 1: " + rom1_path);
83 return absl::InvalidArgumentError(
"Failed to load ROM 2: " + rom2_path);
90 if (format ==
"json") {
96 formatter.
AddField(
"status",
"complete");
97 return absl::OkStatus();
101 const std::vector<uint8_t>& rom2,
105 size_t min_size = std::min(rom1.size(), rom2.size());
106 size_t max_size = std::max(rom1.size(), rom2.size());
109 bool in_diff =
false;
110 uint32_t diff_start = 0;
111 std::vector<uint8_t> old_bytes, new_bytes;
113 for (
size_t i = 0; i < min_size; ++i) {
114 if (rom1[i] != rom2[i]) {
117 diff_start =
static_cast<uint32_t
>(i);
121 old_bytes.push_back(rom1[i]);
122 new_bytes.push_back(rom2[i]);
124 }
else if (in_diff) {
128 diff.
length = old_bytes.size();
137 diff.
description = absl::StrFormat(
"%zu bytes changed", old_bytes.size());
140 summary.
diffs.push_back(diff);
150 diff.
length = old_bytes.size();
155 summary.
diffs.push_back(diff);
160 if (rom1.size() != rom2.size()) {
162 if (rom2.size() > rom1.size()) {
172 for (
const auto& region : kKnownRegions) {
173 if (address >= region.start && address <= region.end) {
174 return region.category;
181 const std::vector<uint8_t>& old_val,
182 const std::vector<uint8_t>& new_val)
const {
186 return absl::StrFormat(
"Graphics data changed (%zu bytes)", old_val.size());
187 }
else if (
category ==
"palettes") {
188 return absl::StrFormat(
"Palette data changed (%zu bytes)", old_val.size());
190 return absl::StrFormat(
"Code modified (%zu bytes)", old_val.size());
192 return absl::StrFormat(
"Sprite data changed (%zu bytes)", old_val.size());
193 }
else if (
category ==
"messages") {
194 return absl::StrFormat(
"Message/text data changed (%zu bytes)", old_val.size());
196 return absl::StrFormat(
"Dungeon data changed (%zu bytes)", old_val.size());
197 }
else if (
category ==
"overworld") {
198 return absl::StrFormat(
"Overworld data changed (%zu bytes)", old_val.size());
201 return absl::StrFormat(
"%zu bytes changed", old_val.size());
205 std::ostringstream
json;
212 json <<
" \"changes_by_category\": {";
215 if (!first)
json <<
", ";
216 json <<
"\"" << cat <<
"\": " << count;
222 json <<
" \"diffs\": [\n";
223 size_t max_diffs = std::min(
size_t(50), summary.
diffs.size());
224 for (
size_t i = 0; i < max_diffs; ++i) {
225 const auto& diff = summary.
diffs[i];
227 json <<
"\"address\": \"" << absl::StrFormat(
"0x%06X", diff.address) <<
"\", ";
228 json <<
"\"length\": " << diff.length <<
", ";
229 json <<
"\"category\": \"" << diff.category <<
"\", ";
230 json <<
"\"description\": \"" << diff.description <<
"\"";
232 if (i < max_diffs - 1)
json <<
",";
236 if (summary.
diffs.size() > 50) {
237 json <<
" // ... and " << (summary.
diffs.size() - 50) <<
" more\n";
247 std::ostringstream text;
249 text <<
"ROM Comparison Summary\n";
250 text <<
"======================\n\n";
252 text <<
"Number of diff regions: " << summary.
num_regions <<
"\n\n";
254 text <<
"Changes by category:\n";
256 text <<
" " << cat <<
": " << count <<
" regions\n";
260 text <<
"Diff regions (first 20):\n";
261 size_t max_diffs = std::min(
size_t(20), summary.
diffs.size());
262 for (
size_t i = 0; i < max_diffs; ++i) {
263 const auto& diff = summary.
diffs[i];
264 text << absl::StrFormat(
" %06X: %s (%zu bytes)\n",
265 diff.address, diff.description, diff.length);
268 if (summary.
diffs.size() > 20) {
269 text <<
" ... and " << (summary.
diffs.size() - 20) <<
" more\n";
282 std::string rom1_path = parser.
GetString(
"rom1").value();
283 std::string rom2_path = parser.
GetString(
"rom2").value();
284 std::string format = parser.
GetString(
"format").value_or(
"json");
290 return absl::InvalidArgumentError(
"Failed to load ROM 1: " + rom1_path);
295 return absl::InvalidArgumentError(
"Failed to load ROM 2: " + rom2_path);
298 std::ostringstream output;
300 if (format ==
"json") {
302 output <<
" \"rom1\": {\"path\": \"" << rom1_path <<
"\", "
303 <<
"\"size\": " << rom1.
size() <<
", "
304 <<
"\"title\": \"" << rom1.
title() <<
"\"},\n";
305 output <<
" \"rom2\": {\"path\": \"" << rom2_path <<
"\", "
306 <<
"\"size\": " << rom2.
size() <<
", "
307 <<
"\"title\": \"" << rom2.
title() <<
"\"},\n";
311 output <<
" \"size_change\": "
312 <<
static_cast<int64_t
>(rom2.
size()) -
static_cast<int64_t
>(rom1.
size())
317 output <<
" \"title_changed\": "
318 << (rom1.
title() != rom2.
title() ?
"true" :
"false") <<
",\n";
322 size_t min_size = std::min(rom1.
size(), rom2.
size());
323 for (
size_t i = 0; i < min_size; ++i) {
324 if (rom1[i] != rom2[i]) total_diffs++;
327 output <<
" \"bytes_different\": " << total_diffs <<
",\n";
328 output <<
" \"percent_changed\": "
329 << absl::StrFormat(
"%.2f", (total_diffs * 100.0) / min_size) <<
"\n";
332 output <<
"ROM Comparison: Changes Analysis\n";
333 output <<
"================================\n\n";
334 output <<
"ROM 1: " << rom1_path <<
" (" << rom1.
size() <<
" bytes)\n";
335 output <<
"ROM 2: " << rom2_path <<
" (" << rom2.
size() <<
" bytes)\n\n";
338 output <<
"Size change: "
339 << (
static_cast<int64_t
>(rom2.
size()) -
static_cast<int64_t
>(rom1.
size()))
344 output <<
"Title changed: '" << rom1.
title() <<
"' -> '" << rom2.
title() <<
"'\n";
348 size_t min_size = std::min(rom1.
size(), rom2.
size());
349 for (
size_t i = 0; i < min_size; ++i) {
350 if (rom1[i] != rom2[i]) total_diffs++;
353 output <<
"Bytes different: " << total_diffs <<
" ("
354 << absl::StrFormat(
"%.2f%%", (total_diffs * 100.0) / min_size) <<
")\n";
357 std::cout << output.str();
358 formatter.
AddField(
"status",
"complete");
359 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 LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
const auto & vector() const
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.