yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_stream_plan_commands.cc
Go to the documentation of this file.
2
3#include <cstdint>
4#include <map>
5#include <string>
6#include <vector>
7
8#include "absl/status/status.h"
9#include "absl/status/statusor.h"
10#include "absl/strings/str_format.h"
12#include "core/hack_manifest.h"
13#include "rom/rom.h"
14#include "util/macro.h"
16
17namespace yaze::cli::handlers {
18namespace {
19
24
25absl::StatusOr<StreamSelection> ParseStreamSelection(const std::string& kind) {
26 if (kind == "objects") {
28 }
29 if (kind == "sprites") {
31 }
32 if (kind == "pot_items") {
34 }
35 return absl::InvalidArgumentError(
36 "--kind must be one of: objects, sprites, pot_items");
37}
38
40 switch (encoding) {
42 return "long24";
44 return "bank16";
45 }
46 return "unknown";
47}
48
50 switch (strategy) {
52 return "copy_on_write";
54 return "repack_all";
55 }
56 return "unknown";
57}
58
60 switch (code) {
62 return "invalid_pointer";
64 return "pointer_outside_data_ranges";
66 return "malformed_stream";
67 }
68 return "unknown";
69}
70
72 switch (kind) {
74 return "suffix";
76 return "interior";
77 }
78 return "unknown";
79}
80
82 const std::vector<zelda3::DungeonStreamPcRange>& intervals) {
83 uint64_t total = 0;
84 for (const auto& interval : intervals) {
85 total += static_cast<uint64_t>(interval.end) - interval.begin;
86 }
87 return total;
88}
89
91 const std::vector<uint32_t>& room_ids) {
92 formatter.BeginArray("room_ids");
93 for (uint32_t room_id : room_ids) {
94 formatter.AddArrayItem(absl::StrFormat("0x%03X", room_id));
95 }
96 formatter.EndArray();
97}
98
100 const std::string& name,
101 const std::vector<zelda3::DungeonStreamPcRange>& intervals) {
102 formatter.BeginArray(name);
103 for (const auto& interval : intervals) {
104 formatter.BeginObject();
105 formatter.AddHexField("begin_pc", interval.begin, 6);
106 formatter.AddHexField("end_pc", interval.end, 6);
107 formatter.AddField("bytes",
108 static_cast<uint64_t>(interval.end) - interval.begin);
109 formatter.EndObject();
110 }
111 formatter.EndArray();
112}
113
115 uint32_t end_pc = 0;
116 std::vector<uint32_t> room_ids;
117};
118
119} // namespace
120
122 const resources::ArgumentParser& parser) {
123 RETURN_IF_ERROR(parser.RequireArgs({"kind", "manifest"}));
124 if (parser.HasFlag("write") || parser.GetString("write").has_value()) {
125 return absl::InvalidArgumentError(
126 "dungeon-stream-plan is read-only and does not accept --write");
127 }
128 return ParseStreamSelection(*parser.GetString("kind")).status();
129}
130
132 Rom* rom, const resources::ArgumentParser& parser,
133 resources::OutputFormatter& formatter) {
134 StreamSelection selection;
135 ASSIGN_OR_RETURN(selection, ParseStreamSelection(*parser.GetString("kind")));
136 const std::string manifest_path = *parser.GetString("manifest");
137
138 core::HackManifest manifest;
139 RETURN_IF_ERROR(manifest.LoadFromFile(manifest_path));
140 const core::DungeonStreamLayout* manifest_layout =
141 manifest.GetDungeonStreamLayout(selection.manifest_type);
142 if (manifest_layout == nullptr) {
143 return absl::FailedPreconditionError(absl::StrFormat(
144 "Manifest does not define dungeon_stream_regions.%s", selection.name));
145 }
146
147 zelda3::DungeonStreamLayout allocator_layout;
148 ASSIGN_OR_RETURN(allocator_layout,
149 core::ToDungeonStreamAllocatorLayout(selection.manifest_type,
150 *manifest_layout));
152 ASSIGN_OR_RETURN(inventory,
153 zelda3::InventoryDungeonStreams(*rom, allocator_layout));
154
155 std::map<uint32_t, UniqueStream> unique_streams;
156 uint64_t valid_stream_slots = 0;
157 for (const auto& stream : inventory.streams) {
158 if (!stream.valid) {
159 continue;
160 }
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);
165 }
166
167 uint64_t suffix_overlaps = 0;
168 uint64_t interior_overlaps = 0;
169 for (const auto& overlap : inventory.overlaps) {
170 if (overlap.kind == zelda3::DungeonStreamOverlapKind::kSuffix) {
171 ++suffix_overlaps;
172 } else {
173 ++interior_overlaps;
174 }
175 }
176 uint64_t exact_aliased_rooms = 0;
177 for (const auto& alias : inventory.aliases) {
178 exact_aliased_rooms += alias.room_ids.size();
179 }
180
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",
186 PointerEncodingName(manifest_layout->pointer_encoding));
187 formatter.AddHexField("pointer_table_snes", manifest_layout->pointer_table,
188 6);
189 formatter.AddHexField("pointer_table_pc", allocator_layout.pointer_table_pc,
190 6);
191 formatter.AddField("pointer_count",
192 static_cast<uint64_t>(allocator_layout.pointer_count));
193 if (manifest_layout->pointer_bank.has_value()) {
194 formatter.AddHexField("pointer_bank", *manifest_layout->pointer_bank, 2);
195 }
196 formatter.AddField("source_size",
197 static_cast<uint64_t>(inventory.source_size));
198 formatter.AddHexField("source_crc32", inventory.source_crc32, 8);
199
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);
210 formatter.AddField("issue_count",
211 static_cast<uint64_t>(inventory.issues.size()));
212
213 formatter.AddField("occupied_bytes",
214 SumIntervalBytes(inventory.occupied_intervals));
215 formatter.AddField("free_bytes", SumIntervalBytes(inventory.free_intervals));
216 formatter.AddField("allocatable_bytes",
217 SumIntervalBytes(inventory.allocatable_free_intervals));
218
219 formatter.BeginArray("unique_streams");
220 for (const auto& [data_pc, stream] : unique_streams) {
221 formatter.BeginObject();
222 formatter.AddHexField("data_pc", data_pc, 6);
223 formatter.AddHexField("end_pc", stream.end_pc, 6);
224 formatter.AddField("bytes", static_cast<uint64_t>(stream.end_pc) - data_pc);
225 AddRoomIds(formatter, stream.room_ids);
226 formatter.EndObject();
227 }
228 formatter.EndArray();
229
230 formatter.BeginArray("exact_aliases");
231 for (const auto& alias : inventory.aliases) {
232 formatter.BeginObject();
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) -
238 alias.data_pc);
239 }
240 AddRoomIds(formatter, alias.room_ids);
241 formatter.EndObject();
242 }
243 formatter.EndArray();
244
245 formatter.BeginArray("overlaps");
246 for (const auto& overlap : inventory.overlaps) {
247 formatter.BeginObject();
248 formatter.AddField("kind", OverlapKindName(overlap.kind));
249 formatter.BeginArray("first_room_ids");
250 for (uint32_t room_id : overlap.first_room_ids) {
251 formatter.AddArrayItem(absl::StrFormat("0x%03X", room_id));
252 }
253 formatter.EndArray();
254 formatter.BeginArray("second_room_ids");
255 for (uint32_t room_id : overlap.second_room_ids) {
256 formatter.AddArrayItem(absl::StrFormat("0x%03X", room_id));
257 }
258 formatter.EndArray();
259 formatter.AddHexField("begin_pc", overlap.intersection.begin, 6);
260 formatter.AddHexField("end_pc", overlap.intersection.end, 6);
261 formatter.AddField("bytes",
262 static_cast<uint64_t>(overlap.intersection.end) -
263 overlap.intersection.begin);
264 formatter.EndObject();
265 }
266 formatter.EndArray();
267
268 formatter.BeginArray("issues");
269 for (const auto& issue : inventory.issues) {
270 formatter.BeginObject();
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);
275 formatter.EndObject();
276 }
277 formatter.EndArray();
278
279 AddIntervals(formatter, "occupied_intervals", inventory.occupied_intervals);
280 AddIntervals(formatter, "free_intervals", inventory.free_intervals);
281 AddIntervals(formatter, "allocatable_intervals",
283
284 if (!inventory.ok()) {
285 return absl::FailedPreconditionError(
286 absl::StrFormat("Dungeon stream inventory contains %zu issue(s)",
287 inventory.issues.size()));
288 }
289 return absl::OkStatus();
290}
291
292} // 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:28
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.
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.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
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)
Definition macro.h:62
void AddIntervals(resources::OutputFormatter &formatter, const std::string &name, const std::vector< zelda3::DungeonStreamPcRange > &intervals)
uint64_t SumIntervalBytes(const std::vector< zelda3::DungeonStreamPcRange > &intervals)
void AddRoomIds(resources::OutputFormatter &formatter, const std::vector< uint32_t > &room_ids)
absl::StatusOr< StreamSelection > ParseStreamSelection(const std::string &kind)
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.
absl::StatusOr< DungeonStreamInventory > InventoryDungeonStreams(const Rom &rom, const DungeonStreamLayout &requested_layout)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
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