yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
resource_labels.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cctype>
6#include <sstream>
7#include <string>
8#include <vector>
9
10#include "absl/strings/str_format.h"
11#include "absl/strings/str_split.h"
12#include "absl/strings/strip.h"
15
16// External declarations for Hyrule Magic sprite names (defined in sprite.cc)
17namespace yaze::zelda3 {
18extern const char* const kSpriteNames[];
19extern const size_t kSpriteNameCount;
20} // namespace yaze::zelda3
21
22namespace yaze {
23namespace zelda3 {
24
25// ============================================================================
26// Resource Type String Conversion
27// ============================================================================
28
30 switch (type) {
32 return "sprite";
34 return "room";
36 return "entrance";
38 return "item";
40 return "overlord";
42 return "overworld_map";
44 return "music";
46 return "graphics";
48 return "room_effect";
50 return "room_tag";
52 return "tile_type";
53 }
54 return "unknown";
55}
56
57ResourceType StringToResourceType(const std::string& type_str) {
58 if (type_str == "sprite")
60 if (type_str == "room")
62 if (type_str == "entrance")
64 if (type_str == "item")
66 if (type_str == "overlord")
68 if (type_str == "overworld_map")
70 if (type_str == "music")
72 if (type_str == "graphics")
74 if (type_str == "room_effect")
76 if (type_str == "room_tag")
78 if (type_str == "tile_type")
80 // Default fallback
82}
83
84namespace {
85
87 int id) {
88 auto lookup = [&](const std::string& key) -> std::string {
89 auto it = labels.find(key);
90 if (it != labels.end() && !it->second.empty()) {
91 return it->second;
92 }
93 return "";
94 };
95
96 // Canonical storage uses decimal keys.
97 if (std::string decimal = lookup(std::to_string(id)); !decimal.empty()) {
98 return decimal;
99 }
100
101 // Compatibility: older Oracle label bundles often use prefixed hex keys.
102 const std::string hex_upper = absl::StrFormat("%X", id);
103 const std::string hex_lower = absl::StrFormat("%x", id);
104 const std::array<std::string, 8> keys = {
105 absl::StrFormat("0x%s", hex_lower), absl::StrFormat("0x%s", hex_upper),
106 absl::StrFormat("0X%s", hex_upper), absl::StrFormat("$%s", hex_upper),
107 absl::StrFormat("0x%02X", id), absl::StrFormat("0x%03X", id),
108 absl::StrFormat("0x%04X", id), absl::StrFormat("$%02X", id),
109 };
110
111 for (const auto& key : keys) {
112 if (std::string label = lookup(key); !label.empty()) {
113 return label;
114 }
115 }
116
117 return "";
118}
119
120} // namespace
121
122// ============================================================================
123// Global Provider Instance
124// ============================================================================
125
127 static ResourceLabelProvider instance;
128 return instance;
129}
130
131// ============================================================================
132// ResourceLabelProvider Implementation
133// ============================================================================
134
135std::string ResourceLabelProvider::GetLabel(ResourceType type, int id) const {
136 std::string type_str = ResourceTypeToString(type);
137
138 // 1. Check project-specific labels first.
139 // Accept decimal keys and prefixed hex keys for compatibility with older
140 // Oracle label bundles.
141 if (project_labels_) {
142 auto type_it = project_labels_->find(type_str);
143 if (type_it != project_labels_->end()) {
144 if (std::string project_label = LookupProjectLabel(type_it->second, id);
145 !project_label.empty()) {
146 return project_label;
147 }
148 }
149 }
150
151 // 2. Check Hack Manifest for ASM-defined labels
153 if (type == ResourceType::kRoomTag) {
154 std::string manifest_label = hack_manifest_->GetRoomTagLabel(id);
155 if (!manifest_label.empty()) {
156 return manifest_label;
157 }
158 }
159 // Future: Add message label lookup if we add GetMessageLabel to HackManifest
160 }
161
162 // 3. For sprites, check Hyrule Magic names if preferred
163 if (type == ResourceType::kSprite && prefer_hmagic_) {
164 std::string hmagic = GetHMagicLabel(type, id);
165 if (!hmagic.empty()) {
166 return hmagic;
167 }
168 }
169
170 // 4. Fall back to vanilla labels
171 return GetVanillaLabel(type, id);
172}
173
174std::string ResourceLabelProvider::GetLabel(const std::string& type_str,
175 int id) const {
176 ResourceType type = StringToResourceType(type_str);
177 return GetLabel(type, id);
178}
179
181 const std::string& label) {
182 if (!project_labels_) {
183 return;
184 }
185 std::string type_str = ResourceTypeToString(type);
186 (*project_labels_)[type_str][std::to_string(id)] = label;
187}
188
190 if (!project_labels_) {
191 return;
192 }
193 std::string type_str = ResourceTypeToString(type);
194 auto type_it = project_labels_->find(type_str);
195 if (type_it != project_labels_->end()) {
196 type_it->second.erase(std::to_string(id));
197 }
198}
199
201 if (!project_labels_) {
202 return false;
203 }
204 std::string type_str = ResourceTypeToString(type);
205 auto type_it = project_labels_->find(type_str);
206 if (type_it == project_labels_->end()) {
207 return false;
208 }
209 return !LookupProjectLabel(type_it->second, id).empty();
210}
211
213 int id) const {
214 switch (type) {
216 const auto& names = Zelda3Labels::GetSpriteNames();
217 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
218 return names[id];
219 }
220 return absl::StrFormat("Sprite %02X", id);
221 }
222 case ResourceType::kRoom: {
223 const auto& names = Zelda3Labels::GetRoomNames();
224 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
225 return names[id];
226 }
227 return absl::StrFormat("Room %03X", id);
228 }
230 const auto& names = Zelda3Labels::GetEntranceNames();
231 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
232 return names[id];
233 }
234 return absl::StrFormat("Entrance %02X", id);
235 }
236 case ResourceType::kItem: {
237 const auto& names = Zelda3Labels::GetItemNames();
238 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
239 return names[id];
240 }
241 return absl::StrFormat("Item %02X", id);
242 }
244 const auto& names = Zelda3Labels::GetOverlordNames();
245 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
246 return names[id];
247 }
248 return absl::StrFormat("Overlord %02X", id);
249 }
251 const auto& names = Zelda3Labels::GetOverworldMapNames();
252 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
253 return names[id];
254 }
255 return absl::StrFormat("Map %02X", id);
256 }
258 const auto& names = Zelda3Labels::GetMusicTrackNames();
259 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
260 return names[id];
261 }
262 return absl::StrFormat("Music %02X", id);
263 }
265 const auto& names = Zelda3Labels::GetGraphicsSheetNames();
266 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
267 return names[id];
268 }
269 return absl::StrFormat("GFX %02X", id);
270 }
272 const auto& names = Zelda3Labels::GetRoomEffectNames();
273 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
274 return names[id];
275 }
276 return absl::StrFormat("Effect %02X", id);
277 }
279 const auto& names = Zelda3Labels::GetRoomTagNames();
280 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
281 return names[id];
282 }
283 return absl::StrFormat("Tag %02X", id);
284 }
286 const auto& names = Zelda3Labels::GetTileTypeNames();
287 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
288 return names[id];
289 }
290 return absl::StrFormat("Tile %02X", id);
291 }
292 }
293 return absl::StrFormat("Unknown %02X", id);
294}
295
297 int id) const {
298 // Hyrule Magic names are only available for sprites
299 if (type != ResourceType::kSprite) {
300 return "";
301 }
302
303 // Use the kSpriteNames array from sprite_names.h
304 if (id >= 0 && static_cast<size_t>(id) < kSpriteNameCount) {
305 return kSpriteNames[id];
306 }
307 return "";
308}
309
311 switch (type) {
313 return 256;
315 return 297;
317 return 133;
319 return static_cast<int>(Zelda3Labels::GetItemNames().size());
321 return static_cast<int>(Zelda3Labels::GetOverlordNames().size());
323 return 160;
325 return static_cast<int>(Zelda3Labels::GetMusicTrackNames().size());
327 return static_cast<int>(Zelda3Labels::GetGraphicsSheetNames().size());
329 return static_cast<int>(Zelda3Labels::GetRoomEffectNames().size());
331 return static_cast<int>(Zelda3Labels::GetRoomTagNames().size());
333 return static_cast<int>(Zelda3Labels::GetTileTypeNames().size());
334 }
335 return 0;
336}
337
340 if (!project_labels_) {
341 return nullptr;
342 }
343 std::string type_str = ResourceTypeToString(type);
344 auto it = project_labels_->find(type_str);
345 if (it == project_labels_->end()) {
346 return nullptr;
347 }
348 return &it->second;
349}
350
356
357// ============================================================================
358// ZScream DefaultNames.txt Import/Export
359// ============================================================================
360
362 const std::string& content) {
363 if (!project_labels_) {
364 return absl::FailedPreconditionError(
365 "Project labels not initialized. Open a project first.");
366 }
367
368 std::istringstream stream(content);
369 std::string line;
370 std::string current_section;
371 int line_index = 0;
372 int line_number = 0;
373
374 while (std::getline(stream, line)) {
375 line_number++;
376
377 // Trim whitespace
378 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
379
380 // Skip empty lines and comments
381 if (trimmed.empty() || trimmed.substr(0, 2) == "//") {
382 continue;
383 }
384
385 // Check for section headers
386 if (trimmed.front() == '[' && trimmed.back() == ']') {
387 current_section = trimmed.substr(1, trimmed.length() - 2);
388 line_index = 0; // Reset line index for new section
389 continue;
390 }
391
392 // Parse the line based on current section
393 if (!current_section.empty()) {
394 ParseZScreamLine(trimmed, current_section, line_index);
395 }
396 }
397
398 return absl::OkStatus();
399}
400
401bool ResourceLabelProvider::ParseZScreamLine(const std::string& line,
402 const std::string& section,
403 int& line_index) {
404 if (!project_labels_) {
405 return false;
406 }
407
408 // Determine resource type and format based on section
409 std::string resource_type;
410 bool has_hex_prefix = false;
411
412 if (section == "Sprites Names") {
413 resource_type = "sprite";
414 has_hex_prefix = true; // Format: "00 Raven"
415 } else if (section == "Rooms Names") {
416 resource_type = "room";
417 has_hex_prefix = false; // Format: just "Room Name" by line order
418 } else if (section == "Chests Items") {
419 resource_type = "item";
420 has_hex_prefix = true; // Format: "00 Fighter sword and shield"
421 } else if (section == "Tags Names") {
422 resource_type = "room_tag";
423 has_hex_prefix = false; // Format: just "Tag Name" by line order
424 } else {
425 // Unknown section, skip
426 return false;
427 }
428
429 std::string id_str;
430 std::string label;
431
432 if (has_hex_prefix) {
433 // Format: "XX Label Name" where XX is hex
434 size_t space_pos = line.find(' ');
435 if (space_pos == std::string::npos || space_pos < 2) {
436 // No space found or too short for hex prefix
437 return false;
438 }
439
440 std::string hex_str = line.substr(0, space_pos);
441 label = line.substr(space_pos + 1);
442
443 // Parse hex to int
444 try {
445 int id = std::stoi(hex_str, nullptr, 16);
446 id_str = std::to_string(id);
447 } catch (...) {
448 return false;
449 }
450 } else {
451 // Line index determines the ID
452 id_str = std::to_string(line_index);
453 label = line;
454 line_index++;
455 }
456
457 // Trim the label
458 label = std::string(absl::StripAsciiWhitespace(label));
459
460 // Store the label
461 if (!label.empty()) {
462 (*project_labels_)[resource_type][id_str] = label;
463 }
464
465 return true;
466}
467
469 std::ostringstream output;
470
471 output << "//Do not use brackets [] in naming\n";
472
473 // Export sprites
474 output << "[Sprites Names]\n";
475 for (int i = 0; i < 256; ++i) {
476 std::string label = GetLabel(ResourceType::kSprite, i);
477 output << absl::StrFormat("%02X %s\n", i, label);
478 }
479
480 // Export rooms
481 output << "\n[Rooms Names]\n";
482 for (int i = 0; i < 297; ++i) {
483 std::string label = GetLabel(ResourceType::kRoom, i);
484 output << label << "\n";
485 }
486
487 // Export items
488 output << "\n[Chests Items]\n";
489 int item_count = GetResourceCount(ResourceType::kItem);
490 for (int i = 0; i < item_count; ++i) {
491 std::string label = GetLabel(ResourceType::kItem, i);
492 output << absl::StrFormat("%02X %s\n", i, label);
493 }
494
495 // Export room tags
496 output << "\n[Tags Names]\n";
498 for (int i = 0; i < tag_count; ++i) {
499 std::string label = GetLabel(ResourceType::kRoomTag, i);
500 output << label << "\n";
501 }
502
503 return output.str();
504}
505
506// ============================================================================
507// Oracle of Secrets Sprite Registry Import
508// ============================================================================
509
511 const std::string& csv_content) {
512 // Create local storage if project_labels_ not set
513 static ProjectLabels local_labels;
514 if (!project_labels_) {
515 project_labels_ = &local_labels;
516 }
517
518 std::istringstream stream(csv_content);
519 std::string line;
520 int line_number = 0;
521 int imported_count = 0;
522
523 while (std::getline(stream, line)) {
524 line_number++;
525
526 // Trim whitespace
527 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
528
529 // Skip empty lines and header row
530 if (trimmed.empty() || line_number == 1) {
531 continue;
532 }
533
534 // Parse CSV: name,id,paths,group,notes,allow_dupe
535 std::vector<std::string> fields = absl::StrSplit(trimmed, ',');
536 if (fields.size() < 2) {
537 continue; // Need at least name and id
538 }
539
540 std::string name = std::string(absl::StripAsciiWhitespace(fields[0]));
541 std::string id_str = std::string(absl::StripAsciiWhitespace(fields[1]));
542
543 // Parse sprite ID (format: $XX or 0xXX or just XX)
544 int sprite_id = -1;
545 if (id_str.empty()) {
546 continue;
547 }
548
549 // Handle $XX format
550 if (id_str[0] == '$') {
551 id_str = id_str.substr(1);
552 }
553 // Handle 0xXX format
554 if (id_str.size() >= 2 && id_str[0] == '0' &&
555 (id_str[1] == 'x' || id_str[1] == 'X')) {
556 id_str = id_str.substr(2);
557 }
558
559 try {
560 sprite_id = std::stoi(id_str, nullptr, 16);
561 } catch (...) {
562 continue; // Skip invalid IDs
563 }
564
565 if (sprite_id < 0 || sprite_id > 255) {
566 continue; // Invalid sprite ID range
567 }
568
569 // Store the sprite name
570 (*project_labels_)["sprite"][std::to_string(sprite_id)] = name;
571 imported_count++;
572 }
573
574 if (imported_count == 0) {
575 return absl::InvalidArgumentError(
576 "No valid sprite entries found in registry CSV");
577 }
578
579 return absl::OkStatus();
580}
581
582} // namespace zelda3
583} // namespace yaze
std::string GetRoomTagLabel(uint8_t tag_id) const
Get the human-readable label for a room tag ID.
bool loaded() const
Check if the manifest has been loaded.
Unified interface for accessing resource labels with project overrides.
std::string ExportToZScreamFormat() const
Export project labels to ZScream DefaultNames.txt format.
void SetProjectLabel(ResourceType type, int id, const std::string &label)
Set a project-specific label override.
bool HasProjectLabel(ResourceType type, int id) const
Check if a project-specific label exists.
void ClearAllProjectLabels()
Clear all project labels.
int GetResourceCount(ResourceType type) const
Get the count of resources for a given type.
std::unordered_map< std::string, std::string > LabelMap
const core::HackManifest * hack_manifest_
std::string GetLabel(ResourceType type, int id) const
Get a label for a resource by type and ID.
bool ParseZScreamLine(const std::string &line, const std::string &section, int &line_index)
std::unordered_map< std::string, LabelMap > ProjectLabels
absl::Status ImportFromZScreamFormat(const std::string &content)
Import labels from ZScream DefaultNames.txt format.
std::string GetVanillaLabel(ResourceType type, int id) const
Get the vanilla (default) label for a resource.
const LabelMap * GetProjectLabelsForType(ResourceType type) const
Get all project labels for a given type.
std::string GetHMagicLabel(ResourceType type, int id) const
Get the Hyrule Magic label for a resource (sprites only)
void ClearProjectLabel(ResourceType type, int id)
Clear a project-specific label (revert to default)
absl::Status ImportOracleSpriteRegistry(const std::string &csv_content)
Import sprite labels from Oracle of Secrets registry.csv format.
std::string LookupProjectLabel(const ResourceLabelProvider::LabelMap &labels, int id)
Zelda 3 specific classes and functions.
ResourceType
Enumeration of all supported resource types for labeling.
const size_t kSpriteNameCount
Definition sprite.cc:292
const char *const kSpriteNames[]
Definition sprite.cc:291
std::string ResourceTypeToString(ResourceType type)
Convert ResourceType enum to string key for storage.
ResourceType StringToResourceType(const std::string &type_str)
Convert string key to ResourceType enum.
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
static const std::vector< std::string > & GetRoomNames()
static const std::vector< std::string > & GetItemNames()
static const std::vector< std::string > & GetEntranceNames()
static const std::vector< std::string > & GetGraphicsSheetNames()
static const std::vector< std::string > & GetMusicTrackNames()
static const std::vector< std::string > & GetTileTypeNames()
static const std::vector< std::string > & GetOverlordNames()
static const std::vector< std::string > & GetSpriteNames()
static const std::vector< std::string > & GetOverworldMapNames()
static const std::vector< std::string > & GetRoomEffectNames()
static const std::vector< std::string > & GetRoomTagNames()