yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
gfx_sheet_inventory_commands.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <sstream>
5#include <string>
6#include <vector>
7
8#include "absl/status/status.h"
9#include "absl/strings/match.h"
10#include "absl/strings/numbers.h"
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_join.h"
13#include "absl/strings/str_split.h"
14#include "absl/strings/strip.h"
16#include "core/project.h"
17#include "nlohmann/json.hpp"
18#include "rom/rom.h"
19#include "util/hex.h"
20#include "util/macro.h"
24
25namespace yaze {
26namespace cli {
27namespace {
28
29absl::StatusOr<std::set<uint16_t>> ParseSheetList(const std::string& value,
30 const char* flag) {
31 std::set<uint16_t> sheets;
32 for (absl::string_view part : absl::StrSplit(value, ',')) {
33 part = absl::StripAsciiWhitespace(part);
34 if (part.empty()) {
35 continue;
36 }
37 // util::ParseHexString instead of absl::SimpleHexAtoi: Ubuntu 22.04's
38 // system Abseil (20210324) predates SimpleHexAtoi.
39 const bool hex = absl::StartsWith(part, "0x") ||
40 absl::StartsWith(part, "0X") ||
41 absl::StartsWith(part, "$");
42 uint32_t sheet = 0;
43 const bool ok = hex ? yaze::util::ParseHexString(part, &sheet)
44 : absl::SimpleAtoi(part, &sheet);
45 if (!ok || sheet >= zelda3::kGfxSheetCount) {
46 return absl::InvalidArgumentError(
47 absl::StrFormat("--%s: '%s' is not a sheet id (0-%zu)", flag,
48 std::string(part), zelda3::kGfxSheetCount - 1));
49 }
50 sheets.insert(static_cast<uint16_t>(sheet));
51 }
52 return sheets;
53}
54
55} // namespace
56
58 Rom* rom, const resources::ArgumentParser& parser,
59 resources::OutputFormatter& formatter) {
60 if (rom == nullptr || !rom->is_loaded()) {
61 return absl::InvalidArgumentError("ROM not loaded");
62 }
64 return absl::FailedPreconditionError(
65 "This ROM uses the Japanese graphics pointer tables (header byte "
66 "$7FD9 = 0); gfx-sheet-inventory supports US-table ROMs only");
67 }
68
71 if (auto reserved = parser.GetString("reserved"); reserved.has_value()) {
72 ASSIGN_OR_RETURN(auto sheets, ParseSheetList(*reserved, "reserved"));
73 options.reserved_sheets.insert(sheets.begin(), sheets.end());
74 }
75 if (auto flagged = parser.GetString("flagged"); flagged.has_value()) {
76 ASSIGN_OR_RETURN(auto sheets, ParseSheetList(*flagged, "flagged"));
77 options.flagged_sheets.insert(sheets.begin(), sheets.end());
78 }
79 if (auto csv_path = parser.GetString("labels-csv"); csv_path.has_value()) {
80 std::ifstream file(*csv_path);
81 if (!file) {
82 return absl::NotFoundError("Cannot read --labels-csv " + *csv_path);
83 }
84 std::stringstream text;
85 text << file.rdbuf();
87 }
88
89 const auto areas = zelda3::CollectOverworldAreaGfx(*rom);
90 const auto rooms = zelda3::CollectRoomGfx(*rom);
92 zelda3::BuildGfxSheetInventory(*rom, areas, rooms, options));
93
94 nlohmann::json document = nlohmann::json::object();
95 document["rom"] = rom->filename();
96 if (auto sha = zelda3::ComputeSha256(rom->filename()); sha.ok()) {
97 document["rom_sha256"] = *sha;
98 }
99 nlohmann::json reserved_rules = nlohmann::json::object();
100 for (uint16_t sheet : options.reserved_sheets) {
101 reserved_rules[absl::StrFormat("0x%02X", sheet)] =
102 "reserved by project config";
103 }
104 document["rules"] = {
105 {"sprite_sheet", "spriteset value + 0x73 = gfx index"},
106 {"dungeon_spriteset", "room header spriteset + 0x40 = spriteset index"},
107 {"reserved", reserved_rules},
108 };
109 document.update(zelda3::GfxSheetInventoryToJson(inventory));
110
111 if (auto out_path = parser.GetString("out"); out_path.has_value()) {
112 std::ofstream out(*out_path, std::ios::trunc);
113 if (!out) {
114 return absl::PermissionDeniedError("Cannot write --out " + *out_path);
115 }
116 out << document.dump(1) << "\n";
117 if (!out) {
118 return absl::DataLossError("Failed writing --out " + *out_path);
119 }
120 }
121
122 if (formatter.IsJson()) {
123 for (const auto& [key, value] : document.items()) {
124 formatter.AddRawJsonField(key, value.dump());
125 }
126 return absl::OkStatus();
127 }
128
129 // Text: one line per sprite sheet that has free space, then totals.
130 int free_blocks = 0;
131 int unreferenced = 0;
132 for (const auto& sheet : inventory.sheets) {
133 const auto free = sheet.FreeBlocks();
134 free_blocks += static_cast<int>(free.size());
135 unreferenced += sheet.unreferenced ? 1 : 0;
136 if (!sheet.sprite_value.has_value() || free.empty()) {
137 continue;
138 }
139 formatter.AddField(
140 absl::StrFormat("sheet 0x%02X (value 0x%02X)", sheet.gfx,
141 *sheet.sprite_value),
142 absl::StrFormat("%zu free 16x16 [%s]%s%s; spritesets %zu, areas %zu, "
143 "rooms %zu",
144 free.size(), absl::StrJoin(free, ","),
145 sheet.flagged ? " FLAGGED" : "",
146 sheet.labels.empty()
147 ? ""
148 : (" " + absl::StrJoin(sheet.labels, "; ")).c_str(),
149 sheet.usage.spritesets.size(),
150 sheet.usage.ow_areas_sprite.size(),
151 sheet.usage.rooms_sprite.size()));
152 }
153 formatter.AddField("sheets", static_cast<int>(inventory.sheets.size()));
154 formatter.AddField("overworld_areas", static_cast<int>(areas.size()));
155 formatter.AddField("rooms", static_cast<int>(rooms.size()));
156 formatter.AddField("free_16x16_blocks", free_blocks);
157 formatter.AddField("unreferenced_sheets", unreferenced);
158 formatter.AddField("reserved_sheets",
159 static_cast<int>(options.reserved_sheets.size()));
160 return absl::OkStatus();
161}
162
163} // namespace cli
164} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto filename() const
Definition rom.h:207
bool is_loaded() const
Definition rom.h:187
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)
Utility for consistent output formatting across commands.
void AddRawJsonField(const std::string &key, const std::string &json)
Add a field whose value is already-serialized JSON (numbers, nested arrays, null)....
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
bool IsJson() const
Check if using JSON format.
Which graphics sheets exist, which tables use them, and which 16x16 blocks are free.
Locates graphics sheets in the ROM and writes edited sheets back.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::StatusOr< std::set< uint16_t > > ParseSheetList(const std::string &value, const char *flag)
zelda3::GfxSheetInventoryOptions BuildGfxSheetInventoryOptions(const project::YazeProject *project)
bool ParseHexString(absl::string_view str, uint64_t *out)
Definition hex.cc:133
std::map< int, OverworldAreaGfxInfo > CollectOverworldAreaGfx(Rom &rom, GameData *game_data)
absl::StatusOr< GfxSheetInventory > BuildGfxSheetInventory(const Rom &rom, const std::map< int, OverworldAreaGfxInfo > &areas, const std::map< int, RoomGfxInfo > &rooms, const GfxSheetInventoryOptions &options)
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
constexpr size_t kGfxSheetCount
bool UsesJapaneseGfxSheetTables(const Rom &rom)
std::map< uint8_t, std::set< std::string > > ParseSpritesetLabelCsv(const std::string &csv_text)
nlohmann::json GfxSheetInventoryToJson(const GfxSheetInventory &inventory)
absl::StatusOr< std::string > ComputeSha256(const std::string &file_path)
std::map< uint8_t, std::set< std::string > > sprite_value_labels
std::vector< GfxSheetInventoryEntry > sheets