yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
atlas_commands.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <optional>
6#include <string>
7#include <vector>
8
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
11#include "core/hack_manifest.h"
12#include "core/project.h"
13#include "rom/rom.h"
14#include "util/macro.h"
15#include "yaze_config.h"
20
21namespace yaze::cli::handlers {
22
23namespace {
24
25namespace fs = std::filesystem;
26namespace atlas = zelda3::atlas;
27
28// YazeProject::Open installs its manifest in the global label provider;
29// restore the previous binding when done.
31 public:
33 : provider_(provider), previous_(provider.hack_manifest()) {}
34 ~ScopedLabelManifestRestore() { provider_.SetHackManifest(previous_); }
37 delete;
38
39 private:
41 const core::HackManifest* previous_ = nullptr;
42};
43
44std::vector<std::string> SplitList(const std::string& value) {
45 std::vector<std::string> out;
46 for (absl::string_view part : absl::StrSplit(value, ',')) {
47 std::string item(part);
48 item.erase(0, item.find_first_not_of(" \t"));
49 item.erase(item.find_last_not_of(" \t") + 1);
50 if (!item.empty()) {
51 out.push_back(item);
52 }
53 }
54 return out;
55}
56
57bool Exists(const std::string& path) {
58 std::error_code ec;
59 return !path.empty() && fs::is_regular_file(path, ec);
60}
61
62} // namespace
63
65 const resources::ArgumentParser& parser) {
66 const std::string view = parser.GetString("view").value_or("dungeon");
67 if (view == "overworld") {
68 return absl::UnimplementedError(
69 "--view overworld is not implemented yet (Atlas P2); use --view "
70 "dungeon");
71 }
72 if (view != "dungeon") {
73 return absl::InvalidArgumentError("--view must be dungeon or overworld");
74 }
75 if (parser.GetString("out").has_value()) {
76 return absl::UnimplementedError(
77 "PNG output (--out) is not implemented yet (Atlas P3); use --json "
78 "<path>");
79 }
80 const auto json = parser.GetString("json");
81 if (!json.has_value() || json->empty()) {
82 return absl::InvalidArgumentError("--json <path> is required");
83 }
84 if (auto defaults = parser.GetString("defaults");
85 defaults.has_value() && *defaults != "vanilla" && *defaults != "none") {
86 return absl::InvalidArgumentError("--defaults must be vanilla or none");
87 }
88 return absl::OkStatus();
89}
90
92 Rom* rom, const resources::ArgumentParser& parser,
93 resources::OutputFormatter& formatter) {
94 resources::CommandInvocationContext invocation_context;
95 if (rom != nullptr && !rom->filename().empty()) {
96 invocation_context.source_rom_path = fs::path(rom->filename());
97 invocation_context.active_rom_path = fs::path(rom->filename());
98 }
99 return ExecuteWithContext(rom, parser, formatter, invocation_context);
100}
101
103 Rom* rom, const resources::ArgumentParser& parser,
105 const resources::CommandInvocationContext& invocation_context) {
107 if (rom == nullptr || !rom->is_loaded()) {
108 return absl::FailedPreconditionError("ROM not loaded");
109 }
110
111 const std::string json_arg = *parser.GetString("json");
112 ASSIGN_OR_RETURN(const auto json_path,
115 invocation_context));
116
117 // ---- Project: registry, warp tags, [atlas] --------------------------
118 std::vector<std::string> annotation_files;
119 std::string defaults = "vanilla";
120 std::vector<std::string> symbol_candidates;
121 core::HackManifest manifest;
122 const std::string project_arg = parser.GetString("project").value_or("");
123 if (!project_arg.empty()) {
124 std::error_code ec;
125 if (!fs::exists(project_arg, ec)) {
126 return absl::NotFoundError(
127 absl::StrFormat("--project path does not exist: %s", project_arg));
128 }
129 if (fs::is_directory(project_arg, ec) &&
130 fs::path(project_arg).extension() != ".yazeproj") {
131 return absl::InvalidArgumentError(absl::StrFormat(
132 "--project %s is a folder; pass the .yaze project file",
133 project_arg));
134 }
135 project::YazeProject project;
136 absl::Status status;
137 {
138 ScopedLabelManifestRestore restore(zelda3::GetResourceLabels());
139 try {
140 status = project.Open(project_arg);
141 } catch (const std::exception& error) {
142 return absl::InvalidArgumentError(absl::StrFormat(
143 "Cannot load project '%s': %s", project_arg, error.what()));
144 }
145 }
146 RETURN_IF_ERROR(status);
147 manifest = project.hack_manifest;
148 annotation_files = project.AtlasAnnotationPaths();
149 defaults = project.atlas.defaults;
150 for (const auto& file : annotation_files) {
151 if (!Exists(file)) {
152 return absl::NotFoundError(absl::StrFormat(
153 "--project %s: [atlas] annotations file %s does not exist",
154 project_arg, file));
155 }
156 }
157 if (manifest.project_registry().dungeons.empty() &&
158 annotation_files.empty()) {
159 return absl::FailedPreconditionError(absl::StrFormat(
160 "--project %s: no dungeon registry (Docs/Dev/Planning/"
161 "dungeons.json beside the code folder, hack manifest or project "
162 "file) and no [atlas] annotations; nothing project-specific would "
163 "load. Drop --project for a ROM-only Atlas",
164 project_arg));
165 }
166 if (!project.atlas.symbols.empty()) {
167 symbol_candidates.push_back(project.AtlasSymbolsPath());
168 }
169 if (!project.symbols_filename.empty()) {
170 symbol_candidates.push_back(
171 project.GetAbsolutePath(project.symbols_filename));
172 }
173 }
174 if (auto extra = parser.GetString("annotations"); extra.has_value()) {
175 for (const auto& file : SplitList(*extra)) {
176 annotation_files.push_back(file);
177 }
178 }
179 if (auto value = parser.GetString("defaults"); value.has_value()) {
180 defaults = *value;
181 }
182 ASSIGN_OR_RETURN(auto annotations,
183 atlas::LoadAtlasAnnotations(annotation_files, defaults));
184
185 // ---- Symbols: --symbols, <rom>.sym, [atlas] symbols, [files] symbols ---
186 std::string symbols_path;
187 if (auto symbols = parser.GetString("symbols"); symbols.has_value()) {
188 if (!Exists(*symbols)) {
189 return absl::NotFoundError(
190 absl::StrFormat("--symbols file does not exist: %s", *symbols));
191 }
192 symbols_path = *symbols;
193 } else if (!annotations.symbol_links.empty()) {
194 std::vector<std::string> candidates;
195 if (!rom->filename().empty()) {
196 candidates.push_back(
197 fs::path(rom->filename()).replace_extension(".sym").string());
198 }
199 candidates.insert(candidates.end(), symbol_candidates.begin(),
200 symbol_candidates.end());
201 for (const auto& candidate : candidates) {
202 if (Exists(candidate)) {
203 symbols_path = candidate;
204 break;
205 }
206 }
207 }
208
209 // ---- Model ------------------------------------------------------------
211 if (!manifest.project_registry().dungeons.empty()) {
212 options.registry = &manifest.project_registry();
213 }
214 options.manifest = &manifest;
215 options.symbol_links = annotations.symbol_links;
216 options.symbols_path = symbols_path;
217 Rom vanilla_rom;
218 if (auto vanilla = parser.GetString("vanilla"); vanilla.has_value()) {
219 RETURN_IF_ERROR(vanilla_rom.LoadFromFile(*vanilla));
220 options.vanilla_rom = &vanilla_rom;
221 }
222 ASSIGN_OR_RETURN(auto input, atlas::CollectDungeonAtlasInput(rom, options));
223 const atlas::DungeonAtlas model =
224 atlas::BuildDungeonAtlas(input, annotations);
225
227 context.project = project_arg;
228 context.generator = std::string("z3ed ") + YAZE_VERSION_STRING;
229 const std::string json = atlas::DungeonAtlasToJson(model, context);
231 invocation_context));
232 {
233 std::ofstream out(json_path, std::ios::binary | std::ios::trunc);
234 if (!out) {
235 return absl::PermissionDeniedError(
236 absl::StrFormat("Cannot write %s", json_arg));
237 }
238 out << json << "\n";
239 if (!out) {
240 return absl::InternalError(absl::StrFormat("Write failed: %s", json_arg));
241 }
242 }
243
244 // ---- Summary ----------------------------------------------------------
245 formatter.AddField("schema", atlas::kDungeonAtlasJsonSchema);
246 formatter.AddField("view", "dungeon");
247 formatter.AddField("json", json_arg);
248 formatter.AddField("rom", model.rom.name);
249 formatter.AddField("rom_sha1", model.rom.sha1);
250 formatter.AddField("project", project_arg);
251 formatter.BeginArray("annotations");
252 for (const auto& file : model.annotation_files) {
253 formatter.AddArrayItem(file);
254 }
255 formatter.EndArray();
256 formatter.AddField("defaults", defaults);
257 formatter.AddField("symbols",
258 symbols_path.empty()
259 ? std::string()
260 : fs::path(symbols_path).filename().string());
261 formatter.AddField("owners_source", model.owners_from_project
262 ? "project"
263 : "derived_from_entrances");
264 formatter.BeginObject("counts");
265 formatter.AddField("free", model.counts.free);
266 formatter.AddField("reclaimable", model.counts.reclaimable);
267 formatter.AddField("in_use", model.counts.in_use);
268 formatter.AddField("orphan", model.counts.orphan);
269 formatter.AddField("reserved", model.counts.reserved);
270 formatter.AddField("interior", model.counts.interior);
271 formatter.AddField("areas", static_cast<int>(model.areas.size()));
272 formatter.AddField("door_edges", static_cast<int>(model.edges.size()));
273 formatter.EndObject();
274 formatter.BeginArray("warnings");
275 for (const auto& warning : model.warnings) {
276 formatter.AddArrayItem(warning);
277 }
278 formatter.EndArray();
279 return absl::OkStatus();
280}
281
282} // namespace yaze::cli::handlers
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
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:491
auto filename() const
Definition rom.h:207
bool is_loaded() const
Definition rom.h:187
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.
absl::Status ExecuteWithContext(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter, const resources::CommandInvocationContext &invocation_context) override
Execute with immutable, invocation-scoped ROM path identity.
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)
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
Loads and queries the hack manifest JSON for yaze-ASM integration.
const ProjectRegistry & project_registry() const
Unified interface for accessing resource labels with project overrides.
#define YAZE_VERSION_STRING
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::vector< std::string > SplitList(const std::string &value)
absl::StatusOr< std::filesystem::path > ResolveStableArtifactPath(const std::filesystem::path &path)
absl::Status RejectArtifactRomAliases(absl::string_view option_name, const std::filesystem::path &artifact_path, const CommandInvocationContext &invocation_context)
absl::StatusOr< DungeonAtlasInput > CollectDungeonAtlasInput(Rom *rom, const DungeonAtlasCollectOptions &options)
DungeonAtlas BuildDungeonAtlas(const DungeonAtlasInput &input, const AtlasAnnotations &annotations)
absl::StatusOr< AtlasAnnotations > LoadAtlasAnnotations(const std::vector< std::string > &files, const std::string &defaults)
std::string DungeonAtlasToJson(const DungeonAtlas &atlas, const DungeonAtlasJsonContext &context, int indent)
Definition atlas_json.cc:72
constexpr char kDungeonAtlasJsonSchema[]
Definition atlas_json.h:15
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::optional< std::filesystem::path > active_rom_path
std::optional< std::filesystem::path > source_rom_path
std::vector< DungeonEntry > dungeons
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
core::HackManifest hack_manifest
Definition project.h:360
std::vector< std::string > AtlasAnnotationPaths() const
Definition project.cc:2089
std::string AtlasSymbolsPath() const
Definition project.cc:2098
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:2028
absl::Status Open(const std::string &project_path)
Definition project.cc:537
std::string symbols_filename
Definition project.h:315
std::vector< SymbolLinkAnnotation > symbol_links
std::vector< DoorEdge > edges
std::vector< AtlasArea > areas
std::vector< std::string > annotation_files
std::vector< std::string > warnings