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
121 int id) {
122 // The project registry can be loaded independently of a hack manifest in
123 // real Oracle projects (see ProjectPathsTest::
124 // OpenInjectsOracleDungeonRoomLabelsIntoProjectFile). Don't gate registry
125 // lookups on manifest->loaded().
126 if (!manifest || !manifest->HasProjectRegistry()) {
127 return "";
128 }
129
130 for (const auto& dungeon : manifest->project_registry().dungeons) {
131 for (const auto& room : dungeon.rooms) {
132 if (room.id == id && !room.name.empty()) {
133 return room.name;
134 }
135 }
136 }
137 return "";
138}
139
140} // namespace
141
142// ============================================================================
143// Global Provider Instance
144// ============================================================================
145
147 static ResourceLabelProvider instance;
148 return instance;
149}
150
151// ============================================================================
152// ResourceLabelProvider Implementation
153// ============================================================================
154
155std::string ResourceLabelProvider::GetLabel(ResourceType type, int id) const {
156 std::string type_str = ResourceTypeToString(type);
157
158 // 1. Room names in the project registry are canonical for Oracle project
159 // files. Prefer them over merged resource labels so stale legacy
160 // oracle_room_labels.json / manually imported labels cannot mask the current
161 // dungeons.json room names.
162 if (type == ResourceType::kRoom) {
163 if (std::string registry_label =
164 LookupProjectRegistryRoomLabel(hack_manifest_, id);
165 !registry_label.empty()) {
166 return registry_label;
167 }
168 }
169
170 // 2. Check project-specific labels.
171 // Accept decimal keys and prefixed hex keys for compatibility with older
172 // Oracle label bundles.
173 if (project_labels_) {
174 auto type_it = project_labels_->find(type_str);
175 if (type_it != project_labels_->end()) {
176 if (std::string project_label = LookupProjectLabel(type_it->second, id);
177 !project_label.empty()) {
178 return project_label;
179 }
180 }
181 }
182
183 // 3. Check Hack Manifest for ASM-defined labels
185 if (type == ResourceType::kRoomTag) {
186 std::string manifest_label = hack_manifest_->GetRoomTagLabel(id);
187 if (!manifest_label.empty()) {
188 return manifest_label;
189 }
190 }
191 // Future: Add message label lookup if we add GetMessageLabel to HackManifest
192 }
193
194 // 4. For sprites, check Hyrule Magic names if preferred
195 if (type == ResourceType::kSprite && prefer_hmagic_) {
196 std::string hmagic = GetHMagicLabel(type, id);
197 if (!hmagic.empty()) {
198 return hmagic;
199 }
200 }
201
202 // 5. Fall back to vanilla labels
203 return GetVanillaLabel(type, id);
204}
205
206std::string ResourceLabelProvider::GetLabel(const std::string& type_str,
207 int id) const {
208 ResourceType type = StringToResourceType(type_str);
209 return GetLabel(type, id);
210}
211
213 const std::string& label) {
214 if (!project_labels_) {
215 return;
216 }
217 std::string type_str = ResourceTypeToString(type);
218 (*project_labels_)[type_str][std::to_string(id)] = label;
219}
220
222 if (!project_labels_) {
223 return;
224 }
225 std::string type_str = ResourceTypeToString(type);
226 auto type_it = project_labels_->find(type_str);
227 if (type_it != project_labels_->end()) {
228 type_it->second.erase(std::to_string(id));
229 }
230}
231
233 if (!project_labels_) {
234 return false;
235 }
236 std::string type_str = ResourceTypeToString(type);
237 auto type_it = project_labels_->find(type_str);
238 if (type_it == project_labels_->end()) {
239 return false;
240 }
241 return !LookupProjectLabel(type_it->second, id).empty();
242}
243
245 int id) const {
246 switch (type) {
248 const auto& names = Zelda3Labels::GetSpriteNames();
249 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
250 return names[id];
251 }
252 return absl::StrFormat("Sprite %02X", id);
253 }
254 case ResourceType::kRoom: {
255 const auto& names = Zelda3Labels::GetRoomNames();
256 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
257 return names[id];
258 }
259 return absl::StrFormat("Room %03X", id);
260 }
262 const auto& names = Zelda3Labels::GetEntranceNames();
263 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
264 return names[id];
265 }
266 return absl::StrFormat("Entrance %02X", id);
267 }
268 case ResourceType::kItem: {
269 const auto& names = Zelda3Labels::GetItemNames();
270 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
271 return names[id];
272 }
273 return absl::StrFormat("Item %02X", id);
274 }
276 const auto& names = Zelda3Labels::GetOverlordNames();
277 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
278 return names[id];
279 }
280 return absl::StrFormat("Overlord %02X", id);
281 }
283 const auto& names = Zelda3Labels::GetOverworldMapNames();
284 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
285 return names[id];
286 }
287 return absl::StrFormat("Map %02X", id);
288 }
290 const auto& names = Zelda3Labels::GetMusicTrackNames();
291 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
292 return names[id];
293 }
294 return absl::StrFormat("Music %02X", id);
295 }
297 const auto& names = Zelda3Labels::GetGraphicsSheetNames();
298 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
299 return names[id];
300 }
301 return absl::StrFormat("GFX %02X", id);
302 }
304 const auto& names = Zelda3Labels::GetRoomEffectNames();
305 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
306 return names[id];
307 }
308 return absl::StrFormat("Effect %02X", id);
309 }
311 const auto& names = Zelda3Labels::GetRoomTagNames();
312 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
313 return names[id];
314 }
315 return absl::StrFormat("Tag %02X", id);
316 }
318 const auto& names = Zelda3Labels::GetTileTypeNames();
319 if (id >= 0 && static_cast<size_t>(id) < names.size()) {
320 return names[id];
321 }
322 return absl::StrFormat("Tile %02X", id);
323 }
324 }
325 return absl::StrFormat("Unknown %02X", id);
326}
327
329 int id) const {
330 // Hyrule Magic names are only available for sprites
331 if (type != ResourceType::kSprite) {
332 return "";
333 }
334
335 // Use the kSpriteNames array from sprite_names.h
336 if (id >= 0 && static_cast<size_t>(id) < kSpriteNameCount) {
337 return kSpriteNames[id];
338 }
339 return "";
340}
341
343 switch (type) {
345 return 256;
347 return 297;
349 return 133;
351 return static_cast<int>(Zelda3Labels::GetItemNames().size());
353 return static_cast<int>(Zelda3Labels::GetOverlordNames().size());
355 return 160;
357 return static_cast<int>(Zelda3Labels::GetMusicTrackNames().size());
359 return static_cast<int>(Zelda3Labels::GetGraphicsSheetNames().size());
361 return static_cast<int>(Zelda3Labels::GetRoomEffectNames().size());
363 return static_cast<int>(Zelda3Labels::GetRoomTagNames().size());
365 return static_cast<int>(Zelda3Labels::GetTileTypeNames().size());
366 }
367 return 0;
368}
369
372 if (!project_labels_) {
373 return nullptr;
374 }
375 std::string type_str = ResourceTypeToString(type);
376 auto it = project_labels_->find(type_str);
377 if (it == project_labels_->end()) {
378 return nullptr;
379 }
380 return &it->second;
381}
382
388
389// ============================================================================
390// ZScream DefaultNames.txt Import/Export
391// ============================================================================
392
394 const std::string& content) {
395 if (!project_labels_) {
396 return absl::FailedPreconditionError(
397 "Project labels not initialized. Open a project first.");
398 }
399
400 std::istringstream stream(content);
401 std::string line;
402 std::string current_section;
403 int line_index = 0;
404 int line_number = 0;
405
406 while (std::getline(stream, line)) {
407 line_number++;
408
409 // Trim whitespace
410 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
411
412 // Skip empty lines and comments
413 if (trimmed.empty() || trimmed.substr(0, 2) == "//") {
414 continue;
415 }
416
417 // Check for section headers
418 if (trimmed.front() == '[' && trimmed.back() == ']') {
419 current_section = trimmed.substr(1, trimmed.length() - 2);
420 line_index = 0; // Reset line index for new section
421 continue;
422 }
423
424 // Parse the line based on current section
425 if (!current_section.empty()) {
426 ParseZScreamLine(trimmed, current_section, line_index);
427 }
428 }
429
430 return absl::OkStatus();
431}
432
433bool ResourceLabelProvider::ParseZScreamLine(const std::string& line,
434 const std::string& section,
435 int& line_index) {
436 if (!project_labels_) {
437 return false;
438 }
439
440 // Determine resource type and format based on section
441 std::string resource_type;
442 bool has_hex_prefix = false;
443
444 if (section == "Sprites Names") {
445 resource_type = "sprite";
446 has_hex_prefix = true; // Format: "00 Raven"
447 } else if (section == "Rooms Names") {
448 resource_type = "room";
449 has_hex_prefix = false; // Format: just "Room Name" by line order
450 } else if (section == "Chests Items") {
451 resource_type = "item";
452 has_hex_prefix = true; // Format: "00 Fighter sword and shield"
453 } else if (section == "Tags Names") {
454 resource_type = "room_tag";
455 has_hex_prefix = false; // Format: just "Tag Name" by line order
456 } else {
457 // Unknown section, skip
458 return false;
459 }
460
461 std::string id_str;
462 std::string label;
463
464 if (has_hex_prefix) {
465 // Format: "XX Label Name" where XX is hex
466 size_t space_pos = line.find(' ');
467 if (space_pos == std::string::npos || space_pos < 2) {
468 // No space found or too short for hex prefix
469 return false;
470 }
471
472 std::string hex_str = line.substr(0, space_pos);
473 label = line.substr(space_pos + 1);
474
475 // Parse hex to int
476 try {
477 int id = std::stoi(hex_str, nullptr, 16);
478 id_str = std::to_string(id);
479 } catch (...) {
480 return false;
481 }
482 } else {
483 // Line index determines the ID
484 id_str = std::to_string(line_index);
485 label = line;
486 line_index++;
487 }
488
489 // Trim the label
490 label = std::string(absl::StripAsciiWhitespace(label));
491
492 // Store the label
493 if (!label.empty()) {
494 (*project_labels_)[resource_type][id_str] = label;
495 }
496
497 return true;
498}
499
501 std::ostringstream output;
502
503 output << "//Do not use brackets [] in naming\n";
504
505 // Export sprites
506 output << "[Sprites Names]\n";
507 for (int i = 0; i < 256; ++i) {
508 std::string label = GetLabel(ResourceType::kSprite, i);
509 output << absl::StrFormat("%02X %s\n", i, label);
510 }
511
512 // Export rooms
513 output << "\n[Rooms Names]\n";
514 for (int i = 0; i < 297; ++i) {
515 std::string label = GetLabel(ResourceType::kRoom, i);
516 output << label << "\n";
517 }
518
519 // Export items
520 output << "\n[Chests Items]\n";
521 int item_count = GetResourceCount(ResourceType::kItem);
522 for (int i = 0; i < item_count; ++i) {
523 std::string label = GetLabel(ResourceType::kItem, i);
524 output << absl::StrFormat("%02X %s\n", i, label);
525 }
526
527 // Export room tags
528 output << "\n[Tags Names]\n";
530 for (int i = 0; i < tag_count; ++i) {
531 std::string label = GetLabel(ResourceType::kRoomTag, i);
532 output << label << "\n";
533 }
534
535 return output.str();
536}
537
538// ============================================================================
539// Oracle of Secrets Sprite Registry Import
540// ============================================================================
541
543 const std::string& csv_content) {
544 // Create local storage if project_labels_ not set
545 static ProjectLabels local_labels;
546 if (!project_labels_) {
547 project_labels_ = &local_labels;
548 }
549
550 std::istringstream stream(csv_content);
551 std::string line;
552 int line_number = 0;
553 int imported_count = 0;
554
555 while (std::getline(stream, line)) {
556 line_number++;
557
558 // Trim whitespace
559 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
560
561 // Skip empty lines and header row
562 if (trimmed.empty() || line_number == 1) {
563 continue;
564 }
565
566 // Parse CSV: name,id,paths,group,notes,allow_dupe
567 std::vector<std::string> fields = absl::StrSplit(trimmed, ',');
568 if (fields.size() < 2) {
569 continue; // Need at least name and id
570 }
571
572 std::string name = std::string(absl::StripAsciiWhitespace(fields[0]));
573 std::string id_str = std::string(absl::StripAsciiWhitespace(fields[1]));
574
575 // Parse sprite ID (format: $XX or 0xXX or just XX)
576 int sprite_id = -1;
577 if (id_str.empty()) {
578 continue;
579 }
580
581 // Handle $XX format
582 if (id_str[0] == '$') {
583 id_str = id_str.substr(1);
584 }
585 // Handle 0xXX format
586 if (id_str.size() >= 2 && id_str[0] == '0' &&
587 (id_str[1] == 'x' || id_str[1] == 'X')) {
588 id_str = id_str.substr(2);
589 }
590
591 try {
592 sprite_id = std::stoi(id_str, nullptr, 16);
593 } catch (...) {
594 continue; // Skip invalid IDs
595 }
596
597 if (sprite_id < 0 || sprite_id > 255) {
598 continue; // Invalid sprite ID range
599 }
600
601 // Store the sprite name
602 (*project_labels_)["sprite"][std::to_string(sprite_id)] = name;
603 imported_count++;
604 }
605
606 if (imported_count == 0) {
607 return absl::InvalidArgumentError(
608 "No valid sprite entries found in registry CSV");
609 }
610
611 return absl::OkStatus();
612}
613
614} // namespace zelda3
615} // namespace yaze
Loads and queries the hack manifest JSON for yaze-ASM integration.
const ProjectRegistry & project_registry() const
bool HasProjectRegistry() const
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 LookupProjectRegistryRoomLabel(const core::HackManifest *manifest, int id)
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:293
const char *const kSpriteNames[]
Definition sprite.cc:292
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.
std::vector< DungeonEntry > dungeons
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()