8#include "absl/status/status.h"
9#include "absl/status/statusor.h"
10#include "absl/strings/str_format.h"
26 if (kind ==
"objects") {
29 if (kind ==
"sprites") {
32 if (kind ==
"pot_items") {
35 return absl::InvalidArgumentError(
36 "--kind must be one of: objects, sprites, pot_items");
52 return "copy_on_write";
62 return "invalid_pointer";
64 return "pointer_outside_data_ranges";
66 return "malformed_stream";
82 const std::vector<zelda3::DungeonStreamPcRange>& intervals) {
84 for (
const auto& interval : intervals) {
85 total +=
static_cast<uint64_t
>(interval.end) - interval.begin;
91 const std::vector<uint32_t>& room_ids) {
93 for (uint32_t room_id : room_ids) {
94 formatter.
AddArrayItem(absl::StrFormat(
"0x%03X", room_id));
100 const std::string&
name,
101 const std::vector<zelda3::DungeonStreamPcRange>& intervals) {
103 for (
const auto& interval : intervals) {
105 formatter.
AddHexField(
"begin_pc", interval.begin, 6);
108 static_cast<uint64_t
>(interval.end) - interval.begin);
125 return absl::InvalidArgumentError(
126 "dungeon-stream-plan is read-only and does not accept --write");
128 return ParseStreamSelection(*parser.
GetString(
"kind")).status();
134 StreamSelection selection;
136 const std::string manifest_path = *parser.
GetString(
"manifest");
142 if (manifest_layout ==
nullptr) {
143 return absl::FailedPreconditionError(absl::StrFormat(
144 "Manifest does not define dungeon_stream_regions.%s", selection.name));
155 std::map<uint32_t, UniqueStream> unique_streams;
156 uint64_t valid_stream_slots = 0;
157 for (
const auto& stream : inventory.
streams) {
161 ++valid_stream_slots;
162 auto& unique = unique_streams[stream.data_pc];
163 unique.end_pc = stream.logical_end_pc;
164 unique.room_ids.push_back(stream.room_id);
167 uint64_t suffix_overlaps = 0;
168 uint64_t interior_overlaps = 0;
169 for (
const auto& overlap : inventory.
overlaps) {
176 uint64_t exact_aliased_rooms = 0;
177 for (
const auto& alias : inventory.
aliases) {
178 exact_aliased_rooms += alias.room_ids.size();
181 formatter.
AddField(
"status", inventory.
ok() ?
"ok" :
"issues");
182 formatter.
AddField(
"kind", selection.name);
183 formatter.
AddField(
"manifest", manifest_path);
184 formatter.
AddField(
"strategy", WriteStrategyName(manifest_layout->
strategy));
185 formatter.
AddField(
"pointer_encoding",
200 formatter.
AddField(
"stream_slot_count",
201 static_cast<uint64_t
>(inventory.
streams.size()));
202 formatter.
AddField(
"valid_stream_slot_count", valid_stream_slots);
203 formatter.
AddField(
"unique_stream_count",
204 static_cast<uint64_t
>(unique_streams.size()));
205 formatter.
AddField(
"exact_alias_group_count",
206 static_cast<uint64_t
>(inventory.
aliases.size()));
207 formatter.
AddField(
"exact_aliased_room_count", exact_aliased_rooms);
208 formatter.
AddField(
"suffix_overlap_count", suffix_overlaps);
209 formatter.
AddField(
"interior_overlap_count", interior_overlaps);
211 static_cast<uint64_t
>(inventory.
issues.size()));
213 formatter.
AddField(
"occupied_bytes",
216 formatter.
AddField(
"allocatable_bytes",
220 for (
const auto& [data_pc, stream] : unique_streams) {
224 formatter.
AddField(
"bytes",
static_cast<uint64_t
>(stream.end_pc) - data_pc);
225 AddRoomIds(formatter, stream.room_ids);
231 for (
const auto& alias : inventory.
aliases) {
233 formatter.
AddHexField(
"data_pc", alias.data_pc, 6);
234 const auto unique = unique_streams.find(alias.data_pc);
235 if (unique != unique_streams.end()) {
236 formatter.
AddHexField(
"end_pc", unique->second.end_pc, 6);
237 formatter.
AddField(
"bytes",
static_cast<uint64_t
>(unique->second.end_pc) -
240 AddRoomIds(formatter, alias.room_ids);
246 for (
const auto& overlap : inventory.
overlaps) {
248 formatter.
AddField(
"kind", OverlapKindName(overlap.kind));
250 for (uint32_t room_id : overlap.first_room_ids) {
251 formatter.
AddArrayItem(absl::StrFormat(
"0x%03X", room_id));
255 for (uint32_t room_id : overlap.second_room_ids) {
256 formatter.
AddArrayItem(absl::StrFormat(
"0x%03X", room_id));
259 formatter.
AddHexField(
"begin_pc", overlap.intersection.begin, 6);
260 formatter.
AddHexField(
"end_pc", overlap.intersection.end, 6);
262 static_cast<uint64_t
>(overlap.intersection.end) -
263 overlap.intersection.begin);
269 for (
const auto& issue : inventory.
issues) {
271 formatter.
AddField(
"code", IssueCodeName(issue.code));
272 formatter.
AddField(
"room_id", absl::StrFormat(
"0x%03X", issue.room_id));
273 formatter.
AddHexField(
"address_pc", issue.address, 6);
274 formatter.
AddField(
"message", issue.message);
280 AddIntervals(formatter,
"free_intervals", inventory.
free_intervals);
281 AddIntervals(formatter,
"allocatable_intervals",
284 if (!inventory.
ok()) {
285 return absl::FailedPreconditionError(
286 absl::StrFormat(
"Dungeon stream inventory contains %zu issue(s)",
287 inventory.
issues.size()));
289 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 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.
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::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
Loads and queries the hack manifest JSON for yaze-ASM integration.
const DungeonStreamLayout * GetDungeonStreamLayout(DungeonStreamType stream) const
Get an explicitly declared dungeon stream layout.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
void AddIntervals(resources::OutputFormatter &formatter, const std::string &name, const std::vector< zelda3::DungeonStreamPcRange > &intervals)
const char * PointerEncodingName(core::DungeonPointerEncoding encoding)
uint64_t SumIntervalBytes(const std::vector< zelda3::DungeonStreamPcRange > &intervals)
const char * IssueCodeName(zelda3::DungeonStreamIssueCode code)
void AddRoomIds(resources::OutputFormatter &formatter, const std::vector< uint32_t > &room_ids)
const char * OverlapKindName(zelda3::DungeonStreamOverlapKind kind)
absl::StatusOr< StreamSelection > ParseStreamSelection(const std::string &kind)
const char * WriteStrategyName(core::DungeonWriteStrategy strategy)
DungeonWriteStrategy
Save strategy allowed for a dungeon stream layout.
DungeonStreamType
Dungeon stream kinds with allocator layouts in the hack manifest.
absl::StatusOr< zelda3::DungeonStreamLayout > ToDungeonStreamAllocatorLayout(DungeonStreamType stream_type, const DungeonStreamLayout &manifest_layout)
DungeonPointerEncoding
Encoding used by entries in a dungeon stream pointer table.
@ kPointerOutsideDataRanges
absl::StatusOr< DungeonStreamInventory > InventoryDungeonStreams(const Rom &rom, const DungeonStreamLayout &requested_layout)
#define RETURN_IF_ERROR(expr)
core::DungeonStreamType manifest_type
std::vector< uint32_t > room_ids
Explicit pointer and storage layout for one dungeon stream.
DungeonWriteStrategy strategy
std::optional< uint8_t > pointer_bank
DungeonPointerEncoding pointer_encoding
std::vector< DungeonStreamPcRange > allocatable_free_intervals
std::vector< DungeonStreamPcRange > occupied_intervals
std::vector< DungeonStreamPcRange > free_intervals
std::vector< DungeonStreamAliasGroup > aliases
std::vector< DungeonStreamIssue > issues
std::vector< DungeonStreamOverlap > overlaps
std::vector< DungeonStreamRecord > streams
uint32_t pointer_table_pc