3#include "absl/strings/numbers.h"
4#include "absl/strings/str_format.h"
5#include "absl/strings/str_split.h"
27 if (!value.has_value()) {
28 return absl::InvalidArgumentError(
29 absl::StrFormat(
"Missing required argument '--%s'",
name));
38 return parsed.status();
40 return parsed.value();
44 const char*
name,
int default_value) {
51 return parsed.status();
53 return parsed.value();
58 return absl::InvalidArgumentError(
59 absl::StrFormat(
"Room ID out of range: 0x%X (expected 0x00-0x%02X)",
62 return absl::OkStatus();
66 if (value < 0 || value > 31) {
67 return absl::InvalidArgumentError(
68 absl::StrFormat(
"%c must be 0-31 (5-bit tile coord)", axis));
70 return absl::OkStatus();
76 save_settings.
backup =
true;
77 auto disk_status = rom->
SaveToFile(save_settings);
78 if (!disk_status.ok()) {
79 formatter.
AddField(
"save_error", std::string(disk_status.message()));
83 formatter.
AddField(
"save_status",
"saved");
84 return absl::OkStatus();
96 auto room_id_str_or = GetRequiredString(parser,
"room");
97 if (!room_id_str_or.ok()) {
98 return room_id_str_or.status();
100 auto sprite_id_str_or = GetRequiredString(parser,
"id");
101 if (!sprite_id_str_or.ok()) {
102 return sprite_id_str_or.status();
104 const std::string room_id_str = room_id_str_or.value();
105 const std::string sprite_id_str = sprite_id_str_or.value();
107 int room_id, sprite_id;
108 if (!ParseHexString(room_id_str, &room_id)) {
109 return absl::InvalidArgumentError(
"Invalid room ID. Must be hex.");
111 if (!ParseHexString(sprite_id_str, &sprite_id)) {
112 return absl::InvalidArgumentError(
"Invalid sprite ID. Must be hex.");
114 auto room_status = ValidateRoomId(room_id);
115 if (!room_status.ok()) {
119 auto x_or = GetRequiredInt(parser,
"x");
121 return x_or.status();
123 auto y_or = GetRequiredInt(parser,
"y");
125 return y_or.status();
127 auto subtype_or = GetOptionalInt(parser,
"subtype", 0);
128 if (!subtype_or.ok()) {
129 return subtype_or.status();
131 auto layer_or = GetOptionalInt(parser,
"layer", 0);
132 if (!layer_or.ok()) {
133 return layer_or.status();
136 int x = x_or.value();
137 int y = y_or.value();
138 int subtype = subtype_or.value();
139 int layer = layer_or.value();
140 bool do_write = parser.
HasFlag(
"write");
145 if (sprite_id < 0 || sprite_id > 0xFF) {
146 return absl::InvalidArgumentError(
"Sprite ID must be 0x00-0xFF");
148 if (subtype < 0 || subtype > 0x1F) {
149 return absl::InvalidArgumentError(
"Subtype must be 0-31 (5-bit flags)");
151 if (layer < 0 || layer > 1) {
152 return absl::InvalidArgumentError(
"Layer must be 0 or 1");
159 int count_before =
static_cast<int>(room.
GetSprites().size());
163 static_cast<uint8_t
>(sprite_id),
static_cast<uint8_t
>(x),
164 static_cast<uint8_t
>(y),
static_cast<uint8_t
>(subtype),
165 static_cast<uint8_t
>(layer));
173 formatter.
AddField(
"subtype", subtype);
175 formatter.
AddField(
"sprites_before", count_before);
178 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
182 if (!save_status.ok()) {
183 formatter.
AddField(
"write_error", std::string(save_status.message()));
187 formatter.
AddField(
"write_status",
"success");
189 auto disk_status = SaveRomWithBackup(rom, formatter);
190 if (!disk_status.ok()) {
197 return absl::OkStatus();
207 auto room_id_str_or = GetRequiredString(parser,
"room");
208 if (!room_id_str_or.ok()) {
209 return room_id_str_or.status();
211 const std::string room_id_str = room_id_str_or.value();
214 if (!ParseHexString(room_id_str, &room_id)) {
215 return absl::InvalidArgumentError(
"Invalid room ID. Must be hex.");
217 auto room_status = ValidateRoomId(room_id);
218 if (!room_status.ok()) {
222 bool do_write = parser.
HasFlag(
"write");
229 int count_before =
static_cast<int>(sprites.size());
232 const bool has_index = parser.
GetString(
"index").has_value();
233 const bool has_x = parser.
GetString(
"x").has_value();
234 const bool has_y = parser.
GetString(
"y").has_value();
235 if (has_index && (has_x || has_y)) {
236 return absl::InvalidArgumentError(
237 "Use either --index or --x/--y, not both");
239 if (!has_index && has_x != has_y) {
240 return absl::InvalidArgumentError(
241 "Both --x and --y are required when removing by position");
243 if (!has_index && !has_x) {
244 return absl::InvalidArgumentError(
245 "Either --index or both --x and --y are required");
248 int remove_index = -1;
250 auto index_or = GetRequiredInt(parser,
"index");
251 if (!index_or.ok()) {
252 return index_or.status();
254 remove_index = index_or.value();
256 auto x_or = GetRequiredInt(parser,
"x");
258 return x_or.status();
260 auto y_or = GetRequiredInt(parser,
"y");
262 return y_or.status();
265 const int x = x_or.value();
266 const int y = y_or.value();
270 for (
int i = 0; i < static_cast<int>(sprites.size()); ++i) {
271 if (sprites[i].x() == x && sprites[i].y() == y) {
276 if (remove_index < 0) {
277 return absl::NotFoundError(absl::StrFormat(
278 "No sprite at (%d, %d) in room 0x%02X", x, y, room_id));
282 if (remove_index < 0 || remove_index >=
static_cast<int>(sprites.size())) {
283 return absl::OutOfRangeError(
284 absl::StrFormat(
"Sprite index %d out of range (room has %d sprites)",
285 remove_index, count_before));
289 const auto& target = sprites[remove_index];
292 formatter.
AddField(
"removed_index", remove_index);
293 formatter.
AddHexField(
"sprite_id", target.id(), 2);
295 formatter.
AddField(
"x", target.x());
296 formatter.
AddField(
"y", target.y());
297 formatter.
AddField(
"sprites_before", count_before);
300 sprites.erase(sprites.begin() + remove_index);
301 formatter.
AddField(
"sprites_after",
static_cast<int>(sprites.size()));
302 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
306 if (!save_status.ok()) {
307 formatter.
AddField(
"write_error", std::string(save_status.message()));
311 formatter.
AddField(
"write_status",
"success");
313 auto disk_status = SaveRomWithBackup(rom, formatter);
314 if (!disk_status.ok()) {
321 return absl::OkStatus();
331 auto room_id_str_or = GetRequiredString(parser,
"room");
332 if (!room_id_str_or.ok()) {
333 return room_id_str_or.status();
335 auto object_id_str_or = GetRequiredString(parser,
"id");
336 if (!object_id_str_or.ok()) {
337 return object_id_str_or.status();
339 const std::string room_id_str = room_id_str_or.value();
340 const std::string object_id_str = object_id_str_or.value();
342 int room_id, object_id;
343 if (!ParseHexString(room_id_str, &room_id)) {
344 return absl::InvalidArgumentError(
"Invalid room ID. Must be hex.");
346 if (!ParseHexString(object_id_str, &object_id)) {
347 return absl::InvalidArgumentError(
"Invalid object ID. Must be hex.");
349 auto room_status = ValidateRoomId(room_id);
350 if (!room_status.ok()) {
353 if (object_id < 0 || object_id > 0xFFFF) {
354 return absl::InvalidArgumentError(
"Object ID must be 0x0000-0xFFFF");
357 auto x_or = GetRequiredInt(parser,
"x");
359 return x_or.status();
361 auto y_or = GetRequiredInt(parser,
"y");
363 return y_or.status();
365 auto size_or = GetOptionalInt(parser,
"size", 0);
367 return size_or.status();
369 auto layer_or = GetOptionalInt(parser,
"layer", 0);
370 if (!layer_or.ok()) {
371 return layer_or.status();
374 int x = x_or.value();
375 int y = y_or.value();
376 int size = size_or.value();
377 int layer = layer_or.value();
378 bool do_write = parser.
HasFlag(
"write");
381 if (x < 0 || x > 63) {
382 return absl::InvalidArgumentError(
"X must be 0-63");
384 if (y < 0 || y > 63) {
385 return absl::InvalidArgumentError(
"Y must be 0-63");
387 if (layer < 0 || layer > 2) {
388 return absl::InvalidArgumentError(
"Layer must be 0, 1, or 2");
390 if (size < 0 || size > 0xFF) {
391 return absl::InvalidArgumentError(
"Size must be 0-255");
397 int count_before =
static_cast<int>(room.
GetTileObjects().size());
401 static_cast<uint8_t
>(x),
static_cast<uint8_t
>(y),
402 static_cast<uint8_t
>(size),
403 static_cast<uint8_t
>(layer));
413 formatter.
AddField(
"object_type", type);
418 formatter.
AddField(
"objects_before", count_before);
422 if (!add_status.ok()) {
423 formatter.
AddField(
"error", std::string(add_status.message()));
430 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
434 if (!save_status.ok()) {
435 formatter.
AddField(
"write_error", std::string(save_status.message()));
439 formatter.
AddField(
"write_status",
"success");
441 auto disk_status = SaveRomWithBackup(rom, formatter);
442 if (!disk_status.ok()) {
449 return absl::OkStatus();
459 auto room_id_str_or = GetRequiredString(parser,
"room");
460 if (!room_id_str_or.ok()) {
461 return room_id_str_or.status();
463 auto tiles_str_or = GetRequiredString(parser,
"tiles");
464 if (!tiles_str_or.ok()) {
465 return tiles_str_or.status();
467 const std::string room_id_str = room_id_str_or.value();
468 const std::string tiles_str = tiles_str_or.value();
471 if (!ParseHexString(room_id_str, &room_id)) {
472 return absl::InvalidArgumentError(
"Invalid room ID. Must be hex.");
474 auto room_status = ValidateRoomId(room_id);
475 if (!room_status.ok()) {
479 bool do_write = parser.
HasFlag(
"write");
485 std::vector<TileSpec> specs;
487 for (absl::string_view entry :
488 absl::StrSplit(tiles_str,
';', absl::SkipEmpty())) {
489 std::vector<std::string> parts =
490 absl::StrSplit(entry,
',', absl::SkipEmpty());
491 if (parts.size() != 3) {
492 return absl::InvalidArgumentError(absl::StrFormat(
493 "Invalid tile spec '%s'. Expected x,y,tile (e.g. 10,5,0xB7)", entry));
498 if (!absl::SimpleAtoi(parts[0], &spec.x)) {
499 return absl::InvalidArgumentError(
500 absl::StrFormat(
"Invalid X coord '%s'", parts[0]));
502 if (!absl::SimpleAtoi(parts[1], &spec.y)) {
503 return absl::InvalidArgumentError(
504 absl::StrFormat(
"Invalid Y coord '%s'", parts[1]));
506 if (!ParseHexString(parts[2], &spec.tile)) {
507 return absl::InvalidArgumentError(
508 absl::StrFormat(
"Invalid tile value '%s'. Must be hex.", parts[2]));
512 if (spec.x < 0 || spec.x > 63 || spec.y < 0 || spec.y > 63) {
513 return absl::InvalidArgumentError(absl::StrFormat(
514 "Tile coords (%d,%d) out of range (0-63)", spec.x, spec.y));
516 if (spec.tile < 0 || spec.tile > 0xFF) {
517 return absl::InvalidArgumentError(
"Tile value must be 0x00-0xFF");
520 specs.push_back(spec);
524 return absl::InvalidArgumentError(
"No tile specs provided");
533 formatter.
AddField(
"tile_count",
static_cast<int>(specs.size()));
534 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
538 for (
const auto& spec : specs) {
553 std::array<zelda3::Room, 1> rooms_arr = {std::move(room)};
555 if (!save_status.ok()) {
556 formatter.
AddField(
"write_error", std::string(save_status.message()));
560 formatter.
AddField(
"write_status",
"success");
562 auto disk_status = SaveRomWithBackup(rom, formatter);
563 if (!disk_status.ok()) {
570 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 SaveToFile(const SaveSettings &settings)
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.
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)
static int DetermineObjectType(uint8_t b1, uint8_t b3)
uint8_t GetCollisionTile(int x, int y) const
absl::Status SaveObjects()
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
absl::Status SaveSprites()
void SetCollisionTile(int x, int y, uint8_t tile)
absl::Status AddObject(const RoomObject &object)
bool has_custom_collision() const
absl::Status ValidateRoomId(int room_id)
absl::StatusOr< int > GetOptionalInt(const resources::ArgumentParser &parser, const char *name, int default_value)
absl::StatusOr< std::string > GetRequiredString(const resources::ArgumentParser &parser, const char *name)
absl::Status ValidateSpriteCoord(int value, char axis)
absl::StatusOr< int > GetRequiredInt(const resources::ArgumentParser &parser, const char *name)
absl::Status SaveRomWithBackup(Rom *rom, resources::OutputFormatter &formatter)
bool ParseHexString(absl::string_view str, int *out)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Room LoadRoomFromRom(Rom *rom, int room_id)
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
const char * ResolveSpriteName(uint16_t id)
#define RETURN_IF_ERROR(expr)