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();
138 absl::StrFormat(
"%zu bytes changed", old_bytes.size());
141 summary.
diffs.push_back(diff);
151 diff.
length = old_bytes.size();
156 summary.
diffs.push_back(diff);
161 if (rom1.size() != rom2.size()) {
163 if (rom2.size() > rom1.size()) {
173 for (
const auto& region : kKnownRegions) {
174 if (address >= region.start && address <= region.end) {
175 return region.category;
182 uint32_t address,
const std::vector<uint8_t>& old_val,
183 const std::vector<uint8_t>& new_val)
const {
187 return absl::StrFormat(
"Graphics data changed (%zu bytes)", old_val.size());
188 }
else if (
category ==
"palettes") {
189 return absl::StrFormat(
"Palette data changed (%zu bytes)", old_val.size());
191 return absl::StrFormat(
"Code modified (%zu bytes)", old_val.size());
193 return absl::StrFormat(
"Sprite data changed (%zu bytes)", old_val.size());
194 }
else if (
category ==
"messages") {
195 return absl::StrFormat(
"Message/text data changed (%zu bytes)",
198 return absl::StrFormat(
"Dungeon data changed (%zu bytes)", old_val.size());
199 }
else if (
category ==
"overworld") {
200 return absl::StrFormat(
"Overworld data changed (%zu bytes)",
204 return absl::StrFormat(
"%zu bytes changed", old_val.size());
208 std::ostringstream
json;
215 json <<
" \"changes_by_category\": {";
220 json <<
"\"" << cat <<
"\": " << count;
226 json <<
" \"diffs\": [\n";
227 size_t max_diffs = std::min(
size_t(50), summary.
diffs.size());
228 for (
size_t i = 0; i < max_diffs; ++i) {
229 const auto& diff = summary.
diffs[i];
231 json <<
"\"address\": \"" << absl::StrFormat(
"0x%06X", diff.address)
233 json <<
"\"length\": " << diff.length <<
", ";
234 json <<
"\"category\": \"" << diff.category <<
"\", ";
235 json <<
"\"description\": \"" << diff.description <<
"\"";
237 if (i < max_diffs - 1)
242 if (summary.
diffs.size() > 50) {
243 json <<
" // ... and " << (summary.
diffs.size() - 50) <<
" more\n";
253 std::ostringstream text;
255 text <<
"ROM Comparison Summary\n";
256 text <<
"======================\n\n";
258 text <<
"Number of diff regions: " << summary.
num_regions <<
"\n\n";
260 text <<
"Changes by category:\n";
262 text <<
" " << cat <<
": " << count <<
" regions\n";
266 text <<
"Diff regions (first 20):\n";
267 size_t max_diffs = std::min(
size_t(20), summary.
diffs.size());
268 for (
size_t i = 0; i < max_diffs; ++i) {
269 const auto& diff = summary.
diffs[i];
270 text << absl::StrFormat(
" %06X: %s (%zu bytes)\n", diff.address,
271 diff.description, diff.length);
274 if (summary.
diffs.size() > 20) {
275 text <<
" ... and " << (summary.
diffs.size() - 20) <<
" more\n";
288 std::string rom1_path = parser.
GetString(
"rom1").value();
289 std::string rom2_path = parser.
GetString(
"rom2").value();
290 std::string format = parser.
GetString(
"format").value_or(
"json");
296 return absl::InvalidArgumentError(
"Failed to load ROM 1: " + rom1_path);
301 return absl::InvalidArgumentError(
"Failed to load ROM 2: " + rom2_path);
304 std::ostringstream output;
306 if (format ==
"json") {
308 output <<
" \"rom1\": {\"path\": \"" << rom1_path <<
"\", "
309 <<
"\"size\": " << rom1.
size() <<
", "
310 <<
"\"title\": \"" << rom1.
title() <<
"\"},\n";
311 output <<
" \"rom2\": {\"path\": \"" << rom2_path <<
"\", "
312 <<
"\"size\": " << rom2.
size() <<
", "
313 <<
"\"title\": \"" << rom2.
title() <<
"\"},\n";
317 output <<
" \"size_change\": "
318 <<
static_cast<int64_t
>(rom2.
size()) -
319 static_cast<int64_t
>(rom1.
size())
324 output <<
" \"title_changed\": "
325 << (rom1.
title() != rom2.
title() ?
"true" :
"false") <<
",\n";
329 size_t min_size = std::min(rom1.
size(), rom2.
size());
330 for (
size_t i = 0; i < min_size; ++i) {
331 if (rom1[i] != rom2[i])
335 output <<
" \"bytes_different\": " << total_diffs <<
",\n";
336 output <<
" \"percent_changed\": "
337 << absl::StrFormat(
"%.2f", (total_diffs * 100.0) / min_size) <<
"\n";
340 output <<
"ROM Comparison: Changes Analysis\n";
341 output <<
"================================\n\n";
342 output <<
"ROM 1: " << rom1_path <<
" (" << rom1.
size() <<
" bytes)\n";
343 output <<
"ROM 2: " << rom2_path <<
" (" << rom2.
size() <<
" bytes)\n\n";
346 output <<
"Size change: "
347 << (
static_cast<int64_t
>(rom2.
size()) -
348 static_cast<int64_t
>(rom1.
size()))
353 output <<
"Title changed: '" << rom1.
title() <<
"' -> '" << rom2.
title()
358 size_t min_size = std::min(rom1.
size(), rom2.
size());
359 for (
size_t i = 0; i < min_size; ++i) {
360 if (rom1[i] != rom2[i])
364 output <<
"Bytes different: " << total_diffs <<
" ("
365 << absl::StrFormat(
"%.2f%%", (total_diffs * 100.0) / min_size)
369 std::cout << output.str();
370 formatter.
AddField(
"status",
"complete");
371 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.