8#include "absl/strings/ascii.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_join.h"
24namespace fs = std::filesystem;
31 : provider_(provider), previous_(provider.hack_manifest()) {}
45 for (
const fs::path candidate : {folder /
"Roms" /
"hack_manifest.json",
46 folder /
"hack_manifest.json"}) {
48 if (fs::exists(candidate, ec)) {
49 auto status = manifest->
LoadFromFile(candidate.string());
70 status = project.
Open(project_arg);
71 }
catch (
const std::exception& error) {
72 return absl::InvalidArgumentError(absl::StrFormat(
73 "Cannot load project '%s': %s", project_arg, error.what()));
80 return absl::OkStatus();
89 const fs::path path(project_arg);
91 if (!fs::exists(path, ec)) {
92 return absl::NotFoundError(
93 absl::StrFormat(
"--project path does not exist: %s", project_arg));
95 const bool is_folder =
96 fs::is_directory(path, ec) && path.extension() !=
".yazeproj";
100 return absl::FailedPreconditionError(absl::StrFormat(
101 "--project %s: no dungeon registry found (Docs/Dev/Planning/"
102 "dungeons.json under %s). Owners would fall back to entrance dungeon "
103 "IDs; pass the folder that holds Docs/Dev/Planning/dungeons.json, or "
104 "drop --project for a ROM-only census",
106 is_folder ?
"that folder"
107 :
"the project's code_folder, the hack manifest's folder "
108 "or the project file's folder"));
110 return absl::OkStatus();
113std::string
Hex(
int room_id) {
114 return absl::StrFormat(
"0x%02X", room_id);
123 const std::vector<int>& rooms) {
125 out += absl::StrFormat(
126 "Room census: %d free, %d reclaimable, %d in use (%d orphan), %d "
131 ?
"project dungeons.json"
132 :
"derived from entrances");
133 out += absl::StrFormat(
"Vanilla baseline: %s\n",
136 :
"none (reclaimable needs one)");
138 out += absl::StrFormat(
"Largest free block: %s\n",
141 out += absl::StrFormat(
"%-6s %-12s %-6s %s\n",
"ROOM",
"STATUS",
"OWNER",
143 for (
int room_id : rooms) {
144 const auto& entry = census.
rooms[room_id];
149 out += absl::StrFormat(
"%-6s %-12s %-6s %s\n",
Hex(room_id), status,
151 absl::StrJoin(entry.reasons,
" | "));
153 out +=
"(* = orphan: in use, but nothing reaches it)\n";
161 return LoadProjectData(project_arg, manifest);
166 if (
auto status = parser.
GetString(
"status"); status.has_value()) {
167 const std::string lowered = absl::AsciiStrToLower(*status);
168 if (lowered !=
"free" && lowered !=
"reclaimable" && lowered !=
"in_use") {
169 return absl::InvalidArgumentError(
170 "--status must be free, reclaimable or in_use");
173 return absl::OkStatus();
180 if (
auto out_path = parser.
GetString(
"emit-vanilla-fingerprints");
181 out_path.has_value()) {
183 if (!fingerprints.ok()) {
184 return fingerprints.status();
186 std::ofstream file(*out_path, std::ios::binary | std::ios::trunc);
188 return absl::PermissionDeniedError(
189 absl::StrFormat(
"Cannot write %s", *out_path));
193 absl::StrFormat(
"%s (%d bytes)",
194 fs::path(rom->
filename()).filename().string(),
195 static_cast<int>(rom->
size())));
196 formatter.
AddField(
"status",
"success");
197 formatter.
AddField(
"written", *out_path);
198 return absl::OkStatus();
203 if (
auto project_arg = parser.
GetString(
"project"); project_arg.has_value()) {
212 if (
auto vanilla_path = parser.
GetString(
"vanilla");
213 vanilla_path.has_value()) {
222 if (!census_or.ok()) {
223 return census_or.status();
227 std::vector<int> rooms;
228 std::optional<zelda3::RoomCensusStatus> status_filter;
229 if (
auto status = parser.
GetString(
"status"); status.has_value()) {
230 const std::string lowered = absl::AsciiStrToLower(*status);
232 : lowered ==
"reclaimable"
236 if (
auto room_arg = parser.
GetString(
"room"); room_arg.has_value()) {
238 if (!util::ParseHexString(*room_arg, &room_id) || room_id < 0 ||
240 return absl::InvalidArgumentError(
241 "--room must be a hex room id 0x000-0x127");
243 rooms.push_back(room_id);
245 for (
const auto& entry : census.
rooms) {
246 if (!status_filter.has_value() || entry.status == *status_filter) {
247 rooms.push_back(entry.room_id);
252 const std::string format =
253 absl::AsciiStrToLower(parser.
GetString(
"format").value_or(
"json"));
254 if (format ==
"table" || format ==
"text") {
255 formatter.
AddRawText(FormatTable(census, rooms));
256 return absl::OkStatus();
262 :
"derived_from_entrances");
266 formatter.
AddField(
"expanded_entrance_tables",
276 auto add_status_list = [&](
const char* key,
auto predicate) {
278 for (
const auto& entry : census.
rooms) {
279 if (predicate(entry)) {
285 add_status_list(
"free_rooms", [](
const auto& e) {
288 add_status_list(
"reclaimable_rooms", [](
const auto& e) {
291 add_status_list(
"orphan_rooms", [](
const auto& e) {
return e.orphan; });
293 formatter.
AddField(
"largest_free_block",
300 formatter.
AddField(
"summary", cluster.summary);
302 for (
int room : cluster.rooms) {
307 for (
int room : cluster.core_rooms) {
316 for (
const auto& owner : census.
owners) {
319 formatter.
AddField(
"name", owner.name);
320 formatter.
AddField(
"interior", owner.interior);
321 formatter.
AddField(
"from_project", owner.from_project);
322 formatter.
AddField(
"rooms", owner.room_count);
328 for (
int room_id : rooms) {
329 const auto& entry = census.
rooms[room_id];
331 formatter.
AddField(
"room", Hex(room_id));
333 formatter.
AddField(
"owner", OwnerId(census, entry));
334 formatter.
AddField(
"owner_name", entry.owner_index >= 0
335 ? census.
owners[entry.owner_index].name
337 formatter.
AddField(
"interior", entry.interior);
338 formatter.
AddField(
"reached", entry.reached);
339 formatter.
AddField(
"empty", entry.empty);
340 formatter.
AddField(
"orphan", entry.orphan);
341 formatter.
AddField(
"objects", entry.object_count);
342 formatter.
AddField(
"sprites", entry.sprite_count);
344 "vanilla_similarity",
345 entry.vanilla_similarity < 0.0f
347 : absl::StrFormat(
"%.2f", entry.vanilla_similarity));
349 for (
const auto& reason : entry.reasons) {
354 for (
const auto& ref : entry.references) {
357 formatter.
AddField(
"strong", ref.strong);
358 if (ref.from_room >= 0) {
359 formatter.
AddField(
"from", Hex(ref.from_room));
361 if (ref.entrance_id >= 0) {
362 formatter.
AddHexField(
"entrance", ref.entrance_id, 2);
364 formatter.
AddField(
"detail", ref.detail);
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())
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
zelda3::ResourceLabelProvider & provider_
~ScopedLabelManifestRestore()
ScopedLabelManifestRestore(const ScopedLabelManifestRestore &)=delete
ScopedLabelManifestRestore(zelda3::ResourceLabelProvider &provider)
ScopedLabelManifestRestore & operator=(const ScopedLabelManifestRestore &)=delete
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)
Loads and queries the hack manifest JSON for yaze-ASM integration.
const ProjectRegistry & project_registry() const
absl::Status LoadProjectRegistry(const std::string &code_folder)
Load project registry data from the code folder.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
Unified interface for accessing resource labels with project overrides.
std::string FormatTable(const zelda3::RoomCensus &census, const std::vector< int > &rooms)
absl::Status LoadProjectData(const std::string &project_arg, core::HackManifest *manifest)
absl::Status LoadProjectFile(const std::string &project_arg, core::HackManifest *manifest)
absl::Status LoadProjectFolder(const fs::path &folder, core::HackManifest *manifest)
std::string Hex(int room_id)
std::string OwnerId(const zelda3::RoomCensus &census, const zelda3::RoomCensusEntry &entry)
absl::Status LoadRoomCensusProject(const std::string &project_arg, core::HackManifest *manifest)
const char * RoomReferenceKindName(RoomReferenceKind kind)
absl::StatusOr< RoomCensus > ComputeRoomCensus(Rom *rom, const RoomCensusOptions &options)
const char * RoomCensusStatusName(RoomCensusStatus status)
absl::StatusOr< std::vector< std::vector< uint16_t > > > ComputeRoomFingerprints(Rom *rom, util::StopToken stop_token)
std::string FormatVanillaFingerprintTable(const std::vector< std::vector< uint16_t > > &fingerprints, const std::string &source_note)
constexpr int kRoomCensusRoomCount
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
#define RETURN_IF_ERROR(expr)
std::vector< DungeonEntry > dungeons
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
absl::Status Open(const std::string &project_path)
const core::ProjectRegistry * project
const core::HackManifest * manifest
std::vector< RoomCensusOwner > owners
std::vector< RoomCensusCluster > free_clusters
bool expanded_entrance_tables
bool has_vanilla_baseline
std::string vanilla_baseline_source
std::vector< RoomCensusEntry > rooms