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"
16
17namespace yaze {
18namespace zelda3 {
19
37 struct TileMapEntry {
38 int rel_x;
39 int rel_y;
40 uint16_t tile_data; // vhopppcc cccccccc
41
42 bool operator==(const TileMapEntry&) const = default;
43 };
44
45 struct BoundingBox {
46 int min_x = 0;
47 int min_y = 0;
48 int max_x = 0;
49 int max_y = 0;
50 int width() const { return (max_x - min_x) + 1; }
51 int height() const { return (max_y - min_y) + 1; }
52 };
53
54 std::vector<TileMapEntry> tiles;
55
56 bool IsEmpty() const { return tiles.empty(); }
57
58 // Compute the bounding box from all tile entries.
59 // If tiles is empty, returns the default-initialized box (min/max=0).
61 if (tiles.empty())
62 return {};
63 BoundingBox bb;
64 bb.min_x = tiles[0].rel_x;
65 bb.max_x = tiles[0].rel_x;
66 bb.min_y = tiles[0].rel_y;
67 bb.max_y = tiles[0].rel_y;
68 for (size_t i = 1; i < tiles.size(); ++i) {
69 if (tiles[i].rel_x < bb.min_x)
70 bb.min_x = tiles[i].rel_x;
71 if (tiles[i].rel_x > bb.max_x)
72 bb.max_x = tiles[i].rel_x;
73 if (tiles[i].rel_y < bb.min_y)
74 bb.min_y = tiles[i].rel_y;
75 if (tiles[i].rel_y > bb.max_y)
76 bb.max_y = tiles[i].rel_y;
77 }
78 return bb;
79 }
80};
81
84 std::vector<uint8_t> source_bytes;
85 std::filesystem::path resolved_path;
86};
87
88// Strict codec for the Oracle custom-object segment format. Encoding preserves
89// sparse positions when the bytecode can represent them and rejects layouts
90// that would otherwise move or overlap tiles.
91absl::StatusOr<CustomObject> DecodeCustomObjectBinary(
92 const std::vector<uint8_t>& data);
93absl::StatusOr<std::vector<uint8_t>> EncodeCustomObjectBinary(
94 const CustomObject& object);
95
96// Resolves a project-relative .bin path using forward-slash separators through
97// its canonical parent. Rejects absolute paths, parent traversal, symlink
98// targets, and paths outside the configured custom-object folder.
99absl::StatusOr<std::filesystem::path> ResolveCustomObjectAssetPath(
100 const std::string& custom_objects_folder, const std::string& filename);
101
102// Loads both the decoded object and the exact source bytes needed for a later
103// stale-write check.
104absl::StatusOr<CustomObjectAsset> LoadCustomObjectAsset(
105 const std::string& custom_objects_folder, const std::string& filename);
106
107// Publishes one existing asset using exact source-byte compare-and-swap,
108// rollback-protected atomic replacement, and strict decoded readback. Returns
109// the canonical bytes that were committed.
110absl::StatusOr<std::vector<uint8_t>> PublishCustomObjectBinary(
111 const std::string& custom_objects_folder, const std::string& filename,
112 const CustomObject& object,
113 const std::vector<uint8_t>& expected_source_bytes,
114 const std::filesystem::path& expected_resolved_path);
115
116// Applies the per-family tilemap transform used by Oracle at draw time while
117// preserving zero payload words as transparent/no-op entries. Source assets
118// and editor write-back deliberately retain their untransformed words.
119uint16_t CustomObjectRuntimeTileWord(int object_id, uint16_t source_word);
120
126
132
137 public:
138 struct State {
139 std::string base_path;
140 std::unordered_map<int, std::vector<std::string>> custom_file_map;
141
142 bool operator==(const State&) const = default;
143 };
144
145 static CustomObjectManager& Get();
146
147 // Initialize with the full path to the custom objects folder
148 // e.g., "/path/to/project/Dungeons/Objects/Data"
149 void Initialize(const std::string& custom_objects_folder);
150
151 // Override object/subtype filename mapping from project config.
152 void SetObjectFileMap(
153 const std::unordered_map<int, std::vector<std::string>>& map);
154 void ClearObjectFileMap();
155 bool HasCustomFileMap() const;
156
157 // Editor sessions share the manager entry point but retain independent
158 // project paths, mappings, decoded assets, and generation tokens.
159 void ActivateRuntimeContext(uint64_t context_id, const State& state);
161 void RemoveRuntimeContext(uint64_t context_id);
162 std::optional<uint64_t> active_runtime_context_id() const {
164 }
165
166 // Load a custom object from a binary file. Successes and failures are cached
167 // in the active context until its assets or configuration are refreshed.
168 absl::StatusOr<std::shared_ptr<CustomObject>> LoadObject(
169 const std::string& filename);
170
171 // Get an object by fixed runtime ID/subtype mapping.
172 // Subtype index maps to the .ObjOffset table
173 absl::StatusOr<std::shared_ptr<CustomObject>> GetObjectInternal(int object_id,
174 int subtype);
175
176 // Get number of subtypes for a custom object ID
177 int GetSubtypeCount(int object_id) const;
178
179 // The current Oracle runtime dispatch tables have fixed capacities. Project
180 // filename mappings may replace assets within these slots but cannot add
181 // new runtime subtypes.
182 static int RuntimeSubtypeCountForObject(int object_id);
183
184 // Canonical Oracle object IDs backed by fixed external runtime assets.
185 static const std::array<int, 3>& RuntimeObjectIds();
186
187 // Reload all cached objects (useful for editor)
188 void ReloadAll();
189
190 // Get the resolved file list for an object_id (empty if none)
191 std::vector<std::string> GetEffectiveFileList(int object_id) const;
192
193 // Returns the built-in subtype filename list for supported object IDs.
194 // Returns an empty list for other IDs.
195 static const std::vector<std::string>& DefaultSubtypeFilenamesForObject(
196 int object_id);
197
198 // Accessors for tile editor write-back
199 const std::string& GetBasePath() const;
200 uint64_t asset_generation() const;
201 absl::StatusOr<CustomObjectSlotBinding> ResolveSlotBinding(int object_id,
202 int subtype) const;
203 std::string ResolveFilename(int object_id, int subtype) const;
204
205 // Snapshot/restore helpers for scoped CLI/runtime feature application.
206 State SnapshotState() const;
207 void RestoreState(const State& state);
208
209 private:
212 std::unordered_map<std::string,
213 absl::StatusOr<std::shared_ptr<CustomObject>>>
215 uint64_t asset_generation = 0;
216 };
217
219
221 const RuntimeContext& ActiveContext() const;
222 uint64_t NextAssetGeneration();
223 void InvalidateCaches();
224 const std::vector<std::string>* ResolveFileList(int object_id) const;
226 std::unordered_map<uint64_t, RuntimeContext> runtime_contexts_;
227 std::optional<uint64_t> active_runtime_context_id_;
229
230 // Mapping from subtype index to filename for ID 0x31
231 static const std::vector<std::string> kSubtype1Filenames;
232 // Mapping from subtype index to filename for ID 0x32
233 static const std::vector<std::string> kSubtype2Filenames;
234 // Mapping from subtype index to filename for ID 0x54
235 static const std::vector<std::string> kSubtype54Filenames;
236};
237
238} // namespace zelda3
239} // namespace yaze
240
241#endif // YAZE_ZELDA3_DUNGEON_CUSTOM_OBJECT_H_
Manages loading and caching of custom object binary files.
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 Initialize(const std::string &custom_objects_folder)
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< 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
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