10#include <unordered_map>
12#include "absl/strings/numbers.h"
13#include "absl/strings/str_format.h"
20#include "nlohmann/json.hpp"
33namespace ow = app::service::OverworldOverlay;
38 : saved_(core::RomSettings::Get().address_overrides()) {
39 core::RomSettings::Get().SetAddressOverrides(project);
42 core::RomSettings::Get().SetAddressOverrides(saved_);
51 std::istringstream ss(s);
53 while (std::getline(ss, tok,
',')) {
54 tok.erase(0, tok.find_first_not_of(
" \t"));
56 tok.erase(tok.find_last_not_of(
" \t") + 1);
61 else if (tok ==
"entrances")
63 else if (tok ==
"exits")
65 else if (tok ==
"holes")
67 else if (tok ==
"items")
69 else if (tok ==
"grid")
71 else if (tok ==
"all")
74 return absl::InvalidArgumentError(
75 absl::StrFormat(
"Unknown overlay '%s' (expected sprites, entrances, "
76 "exits, holes, items, grid, all)",
83 if (value ==
"on" || value ==
"true" || value ==
"1")
85 if (value ==
"off" || value ==
"false" || value ==
"0")
87 return absl::InvalidArgumentError(
"--area-overlay must be on or off");
91 if (
auto out = parser.
GetString(
"out"); out.has_value())
102 return absl::FailedPreconditionError(
"ROM not loaded");
104 return absl::FailedPreconditionError(
105 "Lyra build verification requires --project-context");
107 return absl::FailedPreconditionError(
108 "Save the project's Lyra terrain before building");
111 {
reinterpret_cast<const char*
>(rom->
data()), rom->
size()});
115 const auto terrain_path =
118 const auto size = std::filesystem::file_size(terrain_path, ec);
119 if (ec || size > 4 * 1024 * 1024)
120 return absl::FailedPreconditionError(
121 "Lyra terrain is missing or exceeds 4 MiB");
122 std::ifstream file(terrain_path, std::ios::binary);
124 return absl::PermissionDeniedError(
"Cannot read Lyra terrain");
125 const std::string text{std::istreambuf_iterator<char>(file), {}};
127 return absl::DataLossError(
"Cannot finish reading Lyra terrain");
139 std::unordered_map<std::string, std::string> native_bindings;
140 std::unordered_map<std::string, std::array<uint16_t, 256>> resolved_palettes;
141 std::unordered_map<std::string, nlohmann::json> resolved_assets;
142 auto inspected = nlohmann::json::array();
147 const auto& map = document.authoring().maps[i];
148 for (
int state = 0; state < 3; ++state) {
150 auto found = native_bindings.find(key);
151 if (found == native_bindings.end()) {
153 overworld, map.properties, state));
158 resolved_assets.emplace(key, nlohmann::json::parse(
description));
159 std::array<uint16_t, 256> palette;
160 for (
size_t color = 0; color < palette.size(); ++color)
161 palette[color] = assets.source.current_palette()[color].snes();
162 resolved_palettes.emplace(key, palette);
164 found = native_bindings.emplace(key, std::move(assets.binding)).first;
166 const auto& expected = map.asset_bindings[state];
167 const bool changed = !expected.empty() && expected != found->second;
171 return absl::FailedPreconditionError(absl::StrFormat(
172 "Lyra map $%02X state %d native asset binding changed",
176 inspected.push_back({
179 {
"stored_binding", expected},
180 {
"current_binding", found->second},
181 {
"binding_changed", changed},
182 {
"palette_words", resolved_palettes.at(key)},
183 {
"native_base_binding", found->second},
184 {
"runtime_assignment",
"unassigned"},
185 {
"native_assets", resolved_assets.at(key)},
190 verified_bindings[i][state] = expected.empty() ?
"" : found->second;
196 {
reinterpret_cast<const char*
>(rom->
data()), rom->
size()}))
197 return absl::DataLossError(
"Lyra verification changed the source ROM");
198 formatter.
AddField(
"source_rom_sha256", source_hash);
200 formatter.
AddField(
"terrain_path", terrain_path);
202 formatter.
AddField(
"donor_map", 0x3A);
204 std::ostringstream sheets;
217 formatter.
AddField(
"tile16_definition_fingerprint",
225 formatter.
AddField(
"inspection_only",
true);
226 formatter.
AddField(
"native_resolution_schema",
227 std::string(
"yaze.lyra-native-resolution.v1"));
228 formatter.
AddField(
"unique_native_resolutions",
229 static_cast<int>(resolved_assets.size()));
232 nlohmann::json overrides = nlohmann::json::object();
234 overrides[key] = value;
236 formatter.
AddField(
"rom_address_overrides_sha256",
239 "rom_address_overrides_encoding",
241 "compact sorted-key JSON; uint32 decimal values; no newline"));
242 const auto descriptor_hash =
247 ? nlohmann::json(*descriptor_hash).dump()
250 "project_descriptor_identity_status",
251 std::string(descriptor_hash.ok() ?
"identified" :
"unavailable"));
252 if (!descriptor_hash.ok())
253 formatter.
AddField(
"project_descriptor_identity_error",
254 std::string(descriptor_hash.status().message()));
255 formatter.
AddField(
"asset_binding_mismatches", mismatches);
257 return absl::OkStatus();
261 native_99.set_game_state(0);
262 native_99.LoadAreaGraphics();
264 std::ostringstream native_sheets;
265 native_sheets <<
'[';
266 for (
int i = 0; i < 8; ++i) {
268 native_sheets <<
',';
269 native_sheets << static_cast<int>(native_99.static_graphics(i));
271 native_sheets <<
']';
273 formatter.
AddField(
"main_palette", native_99.render_main_palette());
274 formatter.
AddField(
"palette_set", native_99.area_palette());
275 formatter.
AddField(
"animated_sheet", native_99.animated_sheet());
276 formatter.
AddField(
"backdrop_color", native_99.area_specific_bg_color());
278 std::string bindings =
"[";
283 for (
int state = 0; state < 3; ++state) {
286 bindings +=
'"' + verified_bindings[i][state] +
'"';
291 std::ostringstream definitions;
294 for (
size_t i = 0; i < native.size(); ++i) {
297 const auto& tile = native[i];
298 definitions <<
'[' << tile.tile0_ <<
',' << tile.tile1_ <<
','
299 << tile.tile2_ <<
',' << tile.tile3_ <<
']';
303 formatter.
AddField(
"native_tile32_capacity",
static_cast<int>(native.size()));
304 return absl::OkStatus();
307absl::StatusOr<OverworldRenderGraphicsPreview>
309 const std::optional<std::string>& flag) {
311 const std::string value = flag.value_or(
"");
312 if (value ==
"off" || value ==
"false" || value ==
"0") {
316 const bool required = value ==
"on" || value ==
"true" || value ==
"1";
317 if (!value.empty() && !required) {
318 result.
mode =
"file";
320 }
else if (project !=
nullptr &&
322 result.
mode =
"project";
325 }
else if (required) {
326 return absl::FailedPreconditionError(
327 "--graphics-preview=on needs --project-context with [files] "
328 "overworld_graphics_preview");
330 result.
mode =
"none";
336 preview.status().code(),
337 absl::StrFormat(
"--graphics-preview %s: %s", result.
manifest,
338 preview.status().message()));
340 result.
preview = std::move(preview).value();
347 const auto out = OutputArg(parser);
348 if (!out.has_value() || out->empty()) {
349 return absl::InvalidArgumentError(
"--out <path.png> is required");
351 if (
const auto scale = parser.
GetString(
"scale"); scale.has_value()) {
354 if (
const auto ov = parser.
GetString(
"overlays"); ov.has_value()) {
357 if (
const auto ao = parser.
GetString(
"area-overlay"); ao.has_value()) {
360 if (
const auto gp = parser.
GetString(
"graphics-preview");
361 gp.has_value() && gp->empty()) {
362 return absl::InvalidArgumentError(
363 "--graphics-preview must be on, off, or a manifest path");
365 if (
const auto phase = parser.
GetString(
"phase"); phase.has_value()) {
367 if (!absl::SimpleAtoi(*phase, &value) || value < 0 || value > 2) {
368 return absl::InvalidArgumentError(
"--phase must be 0, 1, or 2");
371 return absl::OkStatus();
378 if (rom !=
nullptr && !rom->
filename().empty()) {
392 if (!util::ParseHexString(*parser.
GetString(
"screen"), &screen)) {
393 return absl::InvalidArgumentError(
"--screen must be hex (e.g. 0x40)");
396 const std::string output_path = *OutputArg(parser);
400 invocation_context));
404 if (
auto ov = parser.
GetString(
"overlays"); ov.has_value()) {
407 if (
auto phase = parser.
GetString(
"phase"); phase.has_value()) {
408 absl::SimpleAtoi(*phase, &req.
phase);
410 if (
auto sc = parser.
GetString(
"scale"); sc.has_value()) {
413 if (
auto ao = parser.
GetString(
"area-overlay"); ao.has_value()) {
422 return absl::FailedPreconditionError(
"ROM not loaded");
427 if (!gd_status.ok()) {
428 return absl::InternalError(
429 absl::StrFormat(
"Failed to load game data: %s", gd_status.message()));
436 invocation_context));
437 std::ofstream out(resolved_output, std::ios::binary);
439 return absl::InternalError(
440 absl::StrFormat(
"Cannot open output file: %s", output_path));
442 out.write(
reinterpret_cast<const char*
>(result.png_data.data()),
443 static_cast<std::streamsize
>(result.png_data.size()));
446 return absl::InternalError(
447 absl::StrFormat(
"Write failed for: %s", output_path));
450 formatter.
AddField(
"requested_screen",
451 absl::StrFormat(
"0x%02X", result.requested_screen));
453 absl::StrFormat(
"0x%02X", result.parent_screen));
454 formatter.
AddField(
"area_size", result.area_size);
455 formatter.
AddField(
"subscreen_overlay",
456 result.subscreen_overlay < 0
457 ? std::string(
"none")
458 : absl::StrFormat(
"0x%02X", result.subscreen_overlay));
459 formatter.
AddField(
"graphics_preview", graphics_preview.mode);
460 if (!graphics_preview.manifest.empty()) {
461 formatter.
AddField(
"graphics_preview_manifest", graphics_preview.manifest);
463 formatter.
AddField(
"graphics_preview_applied", result.graphics_preview);
465 for (
int s : result.screens)
470 : absl::StrFormat(
"%d", req.
phase));
471 formatter.
AddField(
"output", output_path);
472 formatter.
AddField(
"width", result.width);
473 formatter.
AddField(
"height", result.height);
474 formatter.
AddField(
"scale", absl::StrFormat(
"%.2f", req.
scale));
475 formatter.
AddField(
"size_bytes",
static_cast<int>(result.png_data.size()));
478 "marker x/y are area-local pixels at scale 1; tile = x/16, y/16");
480 for (
const auto& m : result.markers) {
483 formatter.
AddField(
"id", absl::StrFormat(
"0x%02X", m.id));
485 formatter.
AddField(
"phase", m.phase);
486 formatter.
AddField(
"list_index", m.list_index);
490 formatter.
AddField(
"tile", absl::StrFormat(
"(%d,%d)", m.x / 16, m.y / 16));
494 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::StatusOr< OverworldRenderResult > RenderOverworldArea(const OverworldRenderRequest &req)
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
virtual bool InspectAssets() const
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
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.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
~ScopedLyraRomAddresses()
core::RomAddressOverrides saved_
ScopedLyraRomAddresses(const core::RomAddressOverrides &project)
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)
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
project::YazeProject * project_
static constexpr int kFirstMap
static absl::StatusOr< LyraTerrainDocument > Parse(std::string_view text, const LyraTerrainProfile &expected_profile)
static constexpr int kMapCount
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
auto mutable_overworld_map(int i)
auto tiles32_unique() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr uint32_t kItems
constexpr uint32_t kHoles
constexpr uint32_t kSprites
constexpr uint32_t kEntrances
constexpr uint32_t kExits
absl::StatusOr< float > ParseRenderScale(absl::string_view value)
absl::StatusOr< bool > ParseAreaOverlay(const std::string &value)
absl::StatusOr< uint32_t > ParseOverworldOverlays(const std::string &s)
std::optional< std::string > OutputArg(const resources::ArgumentParser &parser)
absl::StatusOr< OverworldRenderGraphicsPreview > ResolveOverworldRenderGraphicsPreview(const project::YazeProject *project, const std::optional< std::string > &flag)
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)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< std::string > ComputeSourceArtifactFileSha256(const fs::path &path)
absl::StatusOr< zelda3::OverworldGraphicsPreview > LoadOverworldGraphicsPreview(const std::string &filename)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
absl::StatusOr< std::string > DescribeLyraNativeMapAssets(const core::LyraMapProperties &p, int state, const LyraNativeMapAssets &assets)
std::string LyraMapAssetKey(const core::LyraMapProperties &p, int state)
absl::StatusOr< LyraNativeMapAssets > ResolveLyraMapAssets(Overworld &overworld, const core::LyraMapProperties &p, int state)
absl::Status BindLyraTerrainSource(Overworld &overworld, core::LyraTerrainProfile *profile, gfx::Bitmap *atlas)
#define RETURN_IF_ERROR(expr)
zelda3::OverworldGraphicsPreview graphics_preview
zelda3::OverworldGraphicsPreview preview
std::optional< std::filesystem::path > active_rom_path
std::optional< std::filesystem::path > source_rom_path
std::string animation_fingerprint
std::string palette_fingerprint
std::array< uint8_t, 8 > graphics_sheets
std::string graphics_fingerprint
std::string tile16_definition_fingerprint
std::unordered_map< std::string, uint32_t > addresses
Modern project structure with comprehensive settings consolidation.
bool project_opened() const
std::string lyra_terrain_file
std::string GetAbsolutePath(const std::string &relative_path) const
std::string overworld_graphics_preview
core::RomAddressOverrides rom_address_overrides