yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
custom_object.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <filesystem>
5#include <iostream>
6
7#include "util/log.h"
8
9namespace yaze {
10namespace zelda3 {
11
12const std::vector<std::string> CustomObjectManager::kSubtype1Filenames = {
13 "track_LR.bin", // 00
14 "track_UD.bin", // 01
15 "track_corner_TL.bin", // 02
16 "track_corner_TR.bin", // 03
17 "track_corner_BL.bin", // 04
18 "track_corner_BR.bin", // 05
19 "track_floor_UD.bin", // 06
20 "track_floor_LR.bin", // 07
21 "track_floor_corner_TL.bin", // 08
22 "track_floor_corner_TR.bin", // 09
23 "track_floor_corner_BL.bin", // 10
24 "track_floor_corner_BR.bin", // 11
25 "track_floor_any.bin", // 12
26 "wall_sword_house.bin", // 13
27 "track_any.bin", // 14
28 "small_statue.bin", // 15
29};
30
31const std::vector<std::string> CustomObjectManager::kSubtype2Filenames = {
32 "furnace.bin", // 00
33 "firewood.bin", // 01
34 "ice_chair.bin", // 02
35};
36
38 static CustomObjectManager instance;
39 return instance;
40}
41
42void CustomObjectManager::Initialize(const std::string& custom_objects_folder) {
43 base_path_ = custom_objects_folder;
44 cache_.clear();
45}
46
47absl::StatusOr<std::shared_ptr<CustomObject>> CustomObjectManager::LoadObject(
48 const std::string& filename) {
49 if (cache_.contains(filename)) {
50 return cache_[filename];
51 }
52
53 // base_path_ should be the full path to the custom objects folder (e.g., Dungeons/Objects/Data)
54 std::filesystem::path full_path = std::filesystem::path(base_path_) / filename;
55
56 std::ifstream file(full_path, std::ios::binary);
57 if (!file) {
58 LOG_ERROR("CustomObjectManager", "Failed to open file: %s", full_path.c_str());
59 return absl::NotFoundError("Could not open file: " + full_path.string());
60 }
61
62 // Read entire file into buffer
63 std::vector<uint8_t> buffer((std::istreambuf_iterator<char>(file)),
64 std::istreambuf_iterator<char>());
65
66 auto object_or_error = ParseBinaryData(buffer);
67 if (!object_or_error.ok()) {
68 return object_or_error.status();
69 }
70
71 auto object_ptr = std::make_shared<CustomObject>(std::move(object_or_error.value()));
72 cache_[filename] = object_ptr;
73
74 return object_ptr;
75}
76
77absl::StatusOr<CustomObject> CustomObjectManager::ParseBinaryData(const std::vector<uint8_t>& data) {
78 CustomObject obj;
79 size_t cursor = 0;
80 int current_buffer_pos = 0;
81
82 // Safety check for empty data
83 if (data.empty()) {
84 return obj;
85 }
86
87 // Dungeon room tilemap buffer stride = 128 bytes (64 tiles per row, 2 bytes per tile).
88 // This matches the SNES dungeon room buffer layout where rooms can be up to 64 tiles wide.
89 // The jump offset of 0x80 (128) advances by exactly 1 row.
90 constexpr int kBufferStride = 128;
91
92 while (cursor + 1 < data.size()) {
93 // Read Header (little endian)
94 uint16_t header = data[cursor] | (data[cursor + 1] << 8);
95 cursor += 2;
96
97 if (header == 0) break;
98
99 int count = header & 0x001F;
100 int jump_offset = (header >> 8) & 0xFF;
101
102 // ASM behavior: PHY saves the row start position, tiles are drawn at
103 // incrementing positions, then PLA restores the original position and
104 // the jump offset is added to that original position (not the post-tile position).
105 int row_start_pos = current_buffer_pos;
106
107 // Line Loop
108 for (int i = 0; i < count; ++i) {
109 if (cursor + 1 >= data.size()) {
110 LOG_WARN("CustomObjectManager", "Unexpected end of file parsing object");
111 break;
112 }
113
114 uint16_t tile_data = data[cursor] | (data[cursor + 1] << 8);
115 cursor += 2;
116
117 // Calculate relative X/Y from current buffer position
118 // Buffer stride = 128 bytes (64 tiles per row)
119 int rel_y = current_buffer_pos / kBufferStride;
120 int rel_x = (current_buffer_pos % kBufferStride) / 2; // 2 bytes per tile
121
122 obj.tiles.push_back({rel_x, rel_y, tile_data});
123
124 current_buffer_pos += 2; // Advance 1 tile in buffer
125 }
126
127 // Advance buffer position for next segment from the ROW START, not current position.
128 // This matches the ASM: PLA (restore original Y) then ADC jump_offset.
129 current_buffer_pos = row_start_pos + jump_offset;
130 }
131
132 return obj;
133}
134
135absl::StatusOr<std::shared_ptr<CustomObject>> CustomObjectManager::GetObjectInternal(int object_id, int subtype) {
136 const std::vector<std::string>* list = nullptr;
137 int index = subtype;
138
139 if (object_id == 0x31) {
140 list = &kSubtype1Filenames;
141 } else if (object_id == 0x32) {
142 list = &kSubtype2Filenames;
143 } else if (object_id >= 0x100 && object_id <= 0x103) {
144 // Minecart Track Override for standard corners
145 // 0x100 = TL -> index 2 (track_corner_TL.bin)
146 // 0x101 = BL -> index 4 (track_corner_BL.bin)
147 // 0x102 = TR -> index 3 (track_corner_TR.bin)
148 // 0x103 = BR -> index 5 (track_corner_BR.bin)
149 switch(object_id) {
150 case 0x100: index = 2; break;
151 case 0x101: index = 4; break;
152 case 0x102: index = 3; break;
153 case 0x103: index = 5; break;
154 }
155 list = &kSubtype1Filenames;
156 } else {
157 // Not a supported custom object ID
158 return absl::NotFoundError("Object ID not mapped to custom object");
159 }
160
161 if (index < 0 || index >= static_cast<int>(list->size())) {
162 return absl::OutOfRangeError("Subtype index out of range");
163 }
164
165 return LoadObject((*list)[index]);
166}
167
168int CustomObjectManager::GetSubtypeCount(int object_id) const {
169 if (object_id == 0x31 || (object_id >= 0x100 && object_id <= 0x103)) {
170 return kSubtype1Filenames.size();
171 }
172 if (object_id == 0x32) return kSubtype2Filenames.size();
173 return 0;
174}
175
177 cache_.clear();
178}
179
180} // namespace zelda3
181} // namespace yaze
Manages loading and caching of custom object binary files.
int GetSubtypeCount(int object_id) const
absl::StatusOr< CustomObject > ParseBinaryData(const std::vector< uint8_t > &data)
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< CustomObject > > LoadObject(const std::string &filename)
absl::StatusOr< std::shared_ptr< CustomObject > > GetObjectInternal(int object_id, int subtype)
static const std::vector< std::string > kSubtype2Filenames
void Initialize(const std::string &custom_objects_folder)
std::unordered_map< std::string, std::shared_ptr< CustomObject > > cache_
static const std::vector< std::string > kSubtype1Filenames
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
Represents a decoded custom object (from binary format)
std::vector< TileMapEntry > tiles