yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
custom_object.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_DUNGEON_CUSTOM_OBJECT_H_
2#define YAZE_ZELDA3_DUNGEON_CUSTOM_OBJECT_H_
3
4#include <array>
5#include <cstdint>
6#include <filesystem>
7#include <memory>
8#include <optional>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13#include "absl/status/status.h"
14#include "absl/status/statusor.h"
17
18namespace yaze {
19namespace zelda3 {
20
38 struct TileMapEntry {
39 int rel_x;
40 int rel_y;
41 uint16_t tile_data; // vhopppcc cccccccc
42
43 bool operator==(const TileMapEntry&) const = default;
44 };
45
46 struct BoundingBox {
47 int min_x = 0;
48 int min_y = 0;
49 int max_x = 0;
50 int max_y = 0;
51 int width() const { return (max_x - min_x) + 1; }
52 int height() const { return (max_y - min_y) + 1; }
53 };
54
55 std::vector<TileMapEntry> tiles;
56
57 bool IsEmpty() const { return tiles.empty(); }
58
59 // Compute the bounding box from all tile entries.
60 // If tiles is empty, returns the default-initialized box (min/max=0).
62 if (tiles.empty())
63 return {};
64 BoundingBox bb;
65 bb.min_x = tiles[0].rel_x;
66 bb.max_x = tiles[0].rel_x;
67 bb.min_y = tiles[0].rel_y;
68 bb.max_y = tiles[0].rel_y;
69 for (size_t i = 1; i < tiles.size(); ++i) {
70 if (tiles[i].rel_x < bb.min_x)
71 bb.min_x = tiles[i].rel_x;
72 if (tiles[i].rel_x > bb.max_x)
73 bb.max_x = tiles[i].rel_x;
74 if (tiles[i].rel_y < bb.min_y)
75 bb.min_y = tiles[i].rel_y;
76 if (tiles[i].rel_y > bb.max_y)
77 bb.max_y = tiles[i].rel_y;
78 }
79 return bb;
80 }
81};
82
85 std::vector<uint8_t> source_bytes;
86 std::filesystem::path resolved_path;
87};
88
89// Strict codec for the Oracle custom-object segment format. Encoding preserves
90// sparse positions when the bytecode can represent them and rejects layouts
91// that would otherwise move or overlap tiles.
92absl::StatusOr<CustomObject> DecodeCustomObjectBinary(
93 const std::vector<uint8_t>& data);
94absl::StatusOr<std::vector<uint8_t>> EncodeCustomObjectBinary(
95 const CustomObject& object);
96
97// Resolves a project-relative .bin path using forward-slash separators through
98// its canonical parent. Rejects absolute paths, parent traversal, symlink
99// targets, and paths outside the configured custom-object folder.
100absl::StatusOr<std::filesystem::path> ResolveCustomObjectAssetPath(
101 const std::string& custom_objects_folder, const std::string& filename);
102
103// Loads both the decoded object and the exact source bytes needed for a later
104// stale-write check.
105absl::StatusOr<CustomObjectAsset> LoadCustomObjectAsset(
106 const std::string& custom_objects_folder, const std::string& filename);
107
108// Publishes one existing asset using exact source-byte compare-and-swap,
109// rollback-protected atomic replacement, and strict decoded readback. Returns
110// the canonical bytes that were committed.
111absl::StatusOr<std::vector<uint8_t>> PublishCustomObjectBinary(
112 const std::string& custom_objects_folder, const std::string& filename,
113 const CustomObject& object,
114 const std::vector<uint8_t>& expected_source_bytes,
115 const std::filesystem::path& expected_resolved_path);
116
117// Applies the per-family tilemap transform used by Oracle at draw time while
118// preserving zero payload words as transparent/no-op entries. Source assets
119// and editor write-back deliberately retain their untransformed words.
120uint16_t CustomObjectRuntimeTileWord(int object_id, uint16_t source_word);
121
127
133
134// Immutable decoded assets and routing mode for one background render job.
135// Capture on the session owner thread. Reads never consult files, the active
136// runtime context, mutable cache entries, or global feature flags.
138 public:
139 bool enabled() const { return enabled_; }
140 absl::StatusOr<std::shared_ptr<const DungeonTilePatterns>> tile_patterns()
141 const {
142 return tile_patterns_;
143 }
144 absl::StatusOr<std::shared_ptr<const CustomObject>> GetObject(
145 int object_id, int subtype) const;
146
147 private:
149 bool enabled_ = false;
150 absl::StatusOr<std::shared_ptr<const DungeonTilePatterns>> tile_patterns_ =
151 std::shared_ptr<const DungeonTilePatterns>{};
152 std::unordered_map<
153 int, std::vector<absl::StatusOr<std::shared_ptr<const CustomObject>>>>
155};
156
161 public:
162 struct State {
163 std::string base_path;
164 std::unordered_map<int, std::vector<std::string>> custom_file_map;
166
167 bool operator==(const State&) const = default;
168 };
169
170 static CustomObjectManager& Get();
171
172 // Initialize with the full path to the custom objects folder
173 // e.g., "/path/to/project/Dungeons/Objects/Data"
174 void Initialize(const std::string& custom_objects_folder);
175
176 // Override object/subtype filename mapping from project config.
177 void SetObjectFileMap(
178 const std::unordered_map<int, std::vector<std::string>>& map);
179 void ClearObjectFileMap();
180 // Independent of custom-object dispatch. Empty path restores ROM patterns.
181 void SetTilePatternsSource(const std::string& path);
182 absl::StatusOr<std::shared_ptr<const DungeonTilePatterns>> GetTilePatterns();
183 bool HasCustomFileMap() const;
184
185 // Editor sessions share the manager entry point but retain independent
186 // project paths, mappings, decoded assets, and generation tokens.
187 void ActivateRuntimeContext(uint64_t context_id, const State& state);
189 void RemoveRuntimeContext(uint64_t context_id);
190 std::optional<uint64_t> active_runtime_context_id() const {
192 }
193
194 // Load a custom object from a binary file. Successes and failures are cached
195 // in the active context until its assets or configuration are refreshed.
196 absl::StatusOr<std::shared_ptr<CustomObject>> LoadObject(
197 const std::string& filename);
198
199 // Get an object by fixed runtime ID/subtype mapping.
200 // Subtype index maps to the .ObjOffset table
201 absl::StatusOr<std::shared_ptr<CustomObject>> GetObjectInternal(int object_id,
202 int subtype);
203
204 // Get number of subtypes for a custom object ID
205 int GetSubtypeCount(int object_id) const;
206
207 // The current Oracle runtime dispatch tables have fixed capacities. Project
208 // filename mappings may replace assets within these slots but cannot add
209 // new runtime subtypes.
210 static int RuntimeSubtypeCountForObject(int object_id);
211
212 // Canonical Oracle object IDs backed by fixed external runtime assets.
213 static const std::array<int, 3>& RuntimeObjectIds();
214
215 // Reload all cached objects (useful for editor)
216 void ReloadAll();
217
218 // Get the resolved file list for an object_id (empty if none)
219 std::vector<std::string> GetEffectiveFileList(int object_id) const;
220
221 // Returns the built-in subtype filename list for supported object IDs.
222 // Returns an empty list for other IDs.
223 static const std::vector<std::string>& DefaultSubtypeFilenamesForObject(
224 int object_id);
225
226 // Accessors for tile editor write-back
227 const std::string& GetBasePath() const;
228 uint64_t asset_generation() const;
229 absl::StatusOr<CustomObjectSlotBinding> ResolveSlotBinding(int object_id,
230 int subtype) const;
231 std::string ResolveFilename(int object_id, int subtype) const;
232
233 // Snapshot/restore helpers for scoped CLI/runtime feature application.
234 State SnapshotState() const;
235
236 // Freezes the active session, including cached failures and deep copies of
237 // decoded assets. Call before dispatching work, never from a worker thread.
239 void RestoreState(const State& state);
240
241 private:
244 std::unordered_map<std::string,
245 absl::StatusOr<std::shared_ptr<CustomObject>>>
247 uint64_t asset_generation = 0;
248 std::optional<absl::StatusOr<std::shared_ptr<const DungeonTilePatterns>>>
250 };
251
253
255 const RuntimeContext& ActiveContext() const;
256 uint64_t NextAssetGeneration();
257 void InvalidateCaches();
258 const std::vector<std::string>* ResolveFileList(int object_id) const;
260 std::unordered_map<uint64_t, RuntimeContext> runtime_contexts_;
261 std::optional<uint64_t> active_runtime_context_id_;
263
264 // Mapping from subtype index to filename for ID 0x31
265 static const std::vector<std::string> kSubtype1Filenames;
266 // Mapping from subtype index to filename for ID 0x32
267 static const std::vector<std::string> kSubtype2Filenames;
268 // Mapping from subtype index to filename for ID 0x54
269 static const std::vector<std::string> kSubtype54Filenames;
270};
271
272} // namespace zelda3
273} // namespace yaze
274
275#endif // YAZE_ZELDA3_DUNGEON_CUSTOM_OBJECT_H_
Manages loading and caching of custom object binary files.
CustomObjectRenderSnapshot CaptureRenderSnapshot(bool enabled)
int GetSubtypeCount(int object_id) const
void RestoreState(const State &state)
std::unordered_map< uint64_t, RuntimeContext > runtime_contexts_
absl::StatusOr< CustomObjectSlotBinding > ResolveSlotBinding(int object_id, int subtype) const
const std::string & GetBasePath() const
static const std::array< int, 3 > & RuntimeObjectIds()
static const std::vector< std::string > kSubtype54Filenames
void RemoveRuntimeContext(uint64_t context_id)
static const std::vector< std::string > & DefaultSubtypeFilenamesForObject(int object_id)
void SetObjectFileMap(const std::unordered_map< int, std::vector< std::string > > &map)
static int RuntimeSubtypeCountForObject(int object_id)
std::optional< uint64_t > active_runtime_context_id_
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 SetTilePatternsSource(const std::string &path)
void Initialize(const std::string &custom_objects_folder)
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > GetTilePatterns()
const std::vector< std::string > * ResolveFileList(int object_id) const
std::optional< uint64_t > active_runtime_context_id() const
std::string ResolveFilename(int object_id, int subtype) const
static const std::vector< std::string > kSubtype1Filenames
void ActivateRuntimeContext(uint64_t context_id, const State &state)
std::vector< std::string > GetEffectiveFileList(int object_id) const
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > tile_patterns() const
absl::StatusOr< std::shared_ptr< const CustomObject > > GetObject(int object_id, int subtype) const
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > tile_patterns_
std::unordered_map< int, std::vector< absl::StatusOr< std::shared_ptr< const CustomObject > > > > objects_
absl::StatusOr< CustomObject > DecodeCustomObjectBinary(const std::vector< uint8_t > &data)
absl::StatusOr< std::vector< uint8_t > > EncodeCustomObjectBinary(const CustomObject &object)
uint16_t CustomObjectRuntimeTileWord(int object_id, uint16_t source_word)
absl::StatusOr< fs::path > ResolveCustomObjectAssetPath(const std::string &custom_objects_folder, const std::string &filename)
absl::StatusOr< std::vector< uint8_t > > PublishCustomObjectBinary(const std::string &custom_objects_folder, const std::string &filename, const CustomObject &object, const std::vector< uint8_t > &expected_source_bytes, const fs::path &expected_resolved_path)
absl::StatusOr< CustomObjectAsset > LoadCustomObjectAsset(const std::string &custom_objects_folder, const std::string &filename)
std::filesystem::path resolved_path
std::vector< uint8_t > source_bytes
std::unordered_map< std::string, absl::StatusOr< std::shared_ptr< CustomObject > > > cache
std::optional< absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > > tile_patterns
bool operator==(const State &) const =default
std::unordered_map< int, std::vector< std::string > > custom_file_map
CustomObjectMappingOrigin origin
bool operator==(const TileMapEntry &) const =default
Represents a decoded custom object (from binary format)
BoundingBox GetBoundingBox() const
std::vector< TileMapEntry > tiles