8#include "absl/status/status.h"
9#include "absl/strings/numbers.h"
10#include "absl/strings/str_format.h"
24 "ow_main",
"ow_aux",
"ow_animated",
"hud",
25 "global_sprites",
"armors",
"swords",
"shields",
26 "sprites_aux1",
"sprites_aux2",
"sprites_aux3",
"dungeon_main",
27 "grass",
"3d_object",
"ow_mini_map",
45 auto rgb = color.
rgb();
46 return absl::StrFormat(
"#%02X%02X%02X",
static_cast<int>(rgb.x),
47 static_cast<int>(rgb.y),
static_cast<int>(rgb.z));
52 auto rgb = color.
rgb();
54 return static_cast<int>(0.299 * rgb.x + 0.587 * rgb.y + 0.114 * rgb.z);
61 for (
size_t c = 0; c < palette.
size(); ++c) {
62 const auto& color = palette[c];
63 auto rgb = color.rgb();
64 std::string entry = absl::StrFormat(
66 static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
67 static_cast<int>(rgb.z), color.snes());
77 formatter.
AddField(
"group", group_name);
78 formatter.
AddField(
"palette_count",
static_cast<int>(group.
size()));
81 std::set<uint16_t> unique_snes;
82 int brightness_sum = 0;
86 for (
size_t p = 0; p < group.
size(); ++p) {
88 total_colors +=
static_cast<int>(pal.size());
89 for (
size_t c = 0; c < pal.size(); ++c) {
90 uint16_t snes_val = pal[c].snes();
91 unique_snes.insert(snes_val);
94 darkest = std::min(darkest, br);
95 brightest = std::max(brightest, br);
99 formatter.
AddField(
"total_colors", total_colors);
100 formatter.
AddField(
"unique_colors",
static_cast<int>(unique_snes.size()));
102 if (total_colors > 0) {
104 formatter.
AddField(
"average", brightness_sum / total_colors);
105 formatter.
AddField(
"darkest", darkest);
106 formatter.
AddField(
"brightest", brightest);
113 formatter.
AddHexField(
"rom_address_start", first_addr, 6);
130 auto group_name = parser.
GetString(
"group").value();
138 return absl::NotFoundError(
139 absl::StrFormat(
"Unknown palette group: '%s'. Valid groups: ow_main, "
140 "ow_aux, ow_animated, hud, global_sprites, armors, "
141 "swords, shields, sprites_aux1, sprites_aux2, "
142 "sprites_aux3, dungeon_main, grass, 3d_object, "
148 auto index_result = parser.
GetInt(
"index");
149 if (!index_result.ok() && !absl::IsNotFound(index_result.status())) {
150 return index_result.status();
154 formatter.
AddField(
"group", group_name);
155 formatter.
AddField(
"palette_count",
static_cast<int>(group->size()));
157 if (index_result.ok()) {
158 int idx = index_result.value();
159 if (idx < 0 || idx >=
static_cast<int>(group->size())) {
160 return absl::InvalidArgumentError(absl::StrFormat(
161 "Palette index %d out of range [0, %d)", idx, group->size()));
164 const auto& pal = group->palette_ref(idx);
165 formatter.
AddField(
"palette_index", idx);
166 formatter.
AddField(
"color_count",
static_cast<int>(pal.size()));
167 FormatPaletteColors(pal, formatter);
171 for (
size_t p = 0; p < group->size(); ++p) {
172 const auto& pal = group->palette_ref(p);
174 absl::StrFormat(
"palette %zu (%zu colors)", p, pal.size());
180 if (group->size() <= 6) {
181 for (
size_t p = 0; p < group->size(); ++p) {
182 const auto& pal = group->palette_ref(p);
183 formatter.
BeginObject(absl::StrFormat(
"palette_%zu", p));
184 formatter.
AddField(
"index",
static_cast<int>(p));
185 formatter.
AddField(
"color_count",
static_cast<int>(pal.size()));
186 FormatPaletteColors(pal, formatter);
193 return absl::OkStatus();
202 auto group_name = parser.
GetString(
"group").value();
203 auto palette_str = parser.
GetString(
"palette").value();
204 auto index_str = parser.
GetString(
"index").value();
205 auto color_str = parser.
GetString(
"color").value();
206 bool write = parser.
HasFlag(
"write");
209 if (!absl::SimpleAtoi(palette_str, &palette_idx)) {
210 return absl::InvalidArgumentError(
211 absl::StrFormat(
"Invalid palette index: '%s'", palette_str));
215 if (!absl::SimpleAtoi(index_str, &color_idx)) {
216 return absl::InvalidArgumentError(
217 absl::StrFormat(
"Invalid color index: '%s'", index_str));
221 std::string hex = color_str;
222 if (!hex.empty() && hex[0] ==
'#')
224 if (hex.size() >= 2 && hex[0] ==
'0' && (hex[1] ==
'x' || hex[1] ==
'X'))
227 if (hex.size() != 6) {
228 return absl::InvalidArgumentError(absl::StrFormat(
229 "Color must be a 6-digit hex RGB value (e.g., FF0000). Got: '%s'",
233 unsigned int r_val, g_val, b_val;
234 if (sscanf(hex.c_str(),
"%02x%02x%02x", &r_val, &g_val, &b_val) != 3) {
235 return absl::InvalidArgumentError(
236 absl::StrFormat(
"Failed to parse color hex: '%s'", color_str));
239 uint8_t r =
static_cast<uint8_t
>(r_val);
240 uint8_t g =
static_cast<uint8_t
>(g_val);
241 uint8_t b =
static_cast<uint8_t
>(b_val);
249 return absl::NotFoundError(
250 absl::StrFormat(
"Unknown palette group: '%s'", group_name));
253 if (palette_idx < 0 || palette_idx >=
static_cast<int>(group->size())) {
254 return absl::InvalidArgumentError(absl::StrFormat(
255 "Palette index %d out of range [0, %d)", palette_idx, group->size()));
258 const auto& pal = group->palette_ref(palette_idx);
259 if (color_idx < 0 || color_idx >=
static_cast<int>(pal.size())) {
260 return absl::InvalidArgumentError(absl::StrFormat(
261 "Color index %d out of range [0, %d)", color_idx, pal.size()));
268 auto old_color = group->GetColor(palette_idx, color_idx);
269 std::string old_hex = FormatColorHex(old_color);
272 formatter.
AddField(
"group", group_name);
273 formatter.
AddField(
"palette_index", palette_idx);
274 formatter.
AddField(
"color_index", color_idx);
275 formatter.
AddField(
"old_color", old_hex);
276 formatter.
AddField(
"new_color", FormatColorHex(new_color));
277 formatter.
AddHexField(
"old_snes", old_color.snes(), 4);
283 uint16_t snes_val = new_color.
snes();
286 formatter.
AddField(
"status",
"written");
289 formatter.
AddField(
"status",
"dry_run");
290 formatter.
AddField(
"message",
"Use --write to apply the change to the ROM");
294 return absl::OkStatus();
303 auto group_name_opt = parser.
GetString(
"group");
311 if (group_name_opt.has_value()) {
313 const std::string& group_name = group_name_opt.value();
316 return absl::NotFoundError(
317 absl::StrFormat(
"Unknown palette group: '%s'", group_name));
319 formatter.
AddField(
"analysis_type",
"single_group");
320 AnalyzeGroup(group_name, *group, formatter);
323 formatter.
AddField(
"analysis_type",
"all_groups");
325 int total_palettes = 0;
326 int total_colors = 0;
327 std::set<uint16_t> global_unique;
330 for (
int i = 0; i < kNumGroups; ++i) {
331 const std::string
name = kAllGroupNames[i];
336 int group_colors = 0;
337 for (
size_t p = 0; p < group->size(); ++p) {
339 group_colors +=
static_cast<int>(pal.size());
340 for (
size_t c = 0; c < pal.size(); ++c) {
341 global_unique.insert(pal[c].snes());
344 total_palettes +=
static_cast<int>(group->size());
345 total_colors += group_colors;
347 std::string summary = absl::StrFormat(
"%s: %zu palettes, %d colors",
name,
348 group->size(), group_colors);
353 formatter.
AddField(
"total_groups", kNumGroups);
354 formatter.
AddField(
"total_palettes", total_palettes);
355 formatter.
AddField(
"total_colors", total_colors);
356 formatter.
AddField(
"unique_colors_global",
357 static_cast<int>(global_unique.size()));
362 for (
int i = 0; i < kNumGroups; ++i) {
366 for (
size_t p = 0; p < group->size(); ++p) {
368 for (
size_t c = 0; c < pal.size(); ++c) {
369 int br = ColorBrightness(pal[c]);
370 int bucket = std::min(br / 32, 7);
375 formatter.
AddField(
"0-31_dark", buckets[0]);
376 formatter.
AddField(
"32-63", buckets[1]);
377 formatter.
AddField(
"64-95", buckets[2]);
378 formatter.
AddField(
"96-127", buckets[3]);
379 formatter.
AddField(
"128-159", buckets[4]);
380 formatter.
AddField(
"160-191", buckets[5]);
381 formatter.
AddField(
"192-223", buckets[6]);
382 formatter.
AddField(
"224-255_bright", buckets[7]);
387 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 WriteShort(int addr, uint16_t value)
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
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.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
constexpr uint16_t snes() const
Get SNES 15-bit color.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
std::string FormatColorHex(const gfx::SnesColor &color)
int ColorBrightness(const gfx::SnesColor &color)
void FormatPaletteColors(const gfx::SnesPalette &palette, resources::OutputFormatter &formatter)
constexpr const char * kAllGroupNames[]
void AnalyzeGroup(const std::string &group_name, const gfx::PaletteGroup &group, resources::OutputFormatter &formatter)
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
absl::Status LoadPalettes(const Rom &rom, GameData &data)
#define RETURN_IF_ERROR(expr)
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
gfx::PaletteGroupMap palette_groups