yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
zsprite.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
2#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
3
4#include <charconv>
5#include <cstdint>
6#include <fstream>
7#include <sstream>
8
9#include <string>
10#include <vector>
12
13#include "absl/status/status.h"
14#include "util/macro.h"
15
16namespace yaze {
17namespace editor {
26namespace zsprite {
27
31inline std::string ReadDotNetString(std::istream& is) {
32 uint32_t length = 0;
33 uint8_t byte;
34 int shift = 0;
35 do {
36 is.read(reinterpret_cast<char*>(&byte), 1);
37 if (!is.good())
38 return "";
39 length |= (byte & 0x7F) << shift;
40 shift += 7;
41 } while (byte & 0x80);
42
43 std::string result(length, '\0');
44 if (length > 0) {
45 is.read(&result[0], length);
46 }
47 return result;
48}
49
53inline void WriteDotNetString(std::ostream& os, const std::string& str) {
54 uint32_t length = static_cast<uint32_t>(str.size());
55
56 // Write 7-bit encoded length
57 do {
58 uint8_t byte = length & 0x7F;
59 length >>= 7;
60 if (length > 0) {
61 byte |= 0x80; // Set continuation bit
62 }
63 os.write(reinterpret_cast<const char*>(&byte), 1);
64 } while (length > 0);
65
66 // Write string content
67 if (!str.empty()) {
68 os.write(str.data(), str.size());
69 }
70}
71
72struct OamTile {
73 OamTile() = default;
74 OamTile(uint8_t x, uint8_t y, bool mx, bool my, uint16_t id, uint8_t pal,
75 bool s, uint8_t p)
76 : x(x),
77 y(y),
78 mirror_x(mx),
79 mirror_y(my),
80 id(id),
81 palette(pal),
82 size(s),
83 priority(p) {}
84
85 uint8_t x = 0;
86 uint8_t y = 0;
87 bool mirror_x = false;
88 bool mirror_y = false;
89 uint16_t id = 0;
90 uint8_t palette = 0;
91 bool size = false; // false = 8x8, true = 16x16
92 uint8_t priority = 3;
93 uint8_t z = 0;
94};
95
97 AnimationGroup() = default;
98 AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
99 : frame_name(std::move(fn)),
100 frame_start(fs),
101 frame_end(fe),
102 frame_speed(fsp) {}
103
104 std::string frame_name;
105 uint8_t frame_start = 0;
106 uint8_t frame_end = 0;
107 uint8_t frame_speed = 1;
108 std::vector<OamTile> Tiles;
109};
110
111struct Frame {
112 std::vector<OamTile> Tiles;
113};
114
116 UserRoutine() = default;
117 UserRoutine(std::string n, std::string c)
118 : name(std::move(n)), code(std::move(c)) {}
119
120 std::string name;
121 std::string code;
122};
123
124struct SubEditor {
125 std::vector<Frame> Frames;
126 std::vector<UserRoutine> user_routines;
127};
128
130 bool IsChecked = false;
131 std::string Text;
132};
133
134struct ZSprite {
135 public:
139 absl::Status Load(const std::string& filename) {
140 std::ifstream input(filename, std::ios::binary);
141 if (!input.is_open()) {
142 return absl::NotFoundError("File not found: " + filename);
143 }
144
145 std::string content(8 * 1024 * 1024 + 1, '\0');
146 input.read(content.data(), content.size());
147 content.resize(static_cast<size_t>(input.gcount()));
148 if (input.bad())
149 return absl::DataLossError("Cannot read ZSM asset");
151 std::istringstream fs(content, std::ios::binary);
152
153 // Clear existing data
154 Reset();
155
156 // Read animation count
157 int32_t animation_count = 0;
158 fs.read(reinterpret_cast<char*>(&animation_count), sizeof(int32_t));
159
160 // Read animations
161 for (int i = 0; i < animation_count; i++) {
162 std::string aname = ReadDotNetString(fs);
163 uint8_t afs, afe, afspeed;
164 fs.read(reinterpret_cast<char*>(&afs), sizeof(uint8_t));
165 fs.read(reinterpret_cast<char*>(&afe), sizeof(uint8_t));
166 fs.read(reinterpret_cast<char*>(&afspeed), sizeof(uint8_t));
167 animations.emplace_back(afs, afe, afspeed, aname);
168 }
169
170 // Read frame count
171 int32_t frame_count = 0;
172 fs.read(reinterpret_cast<char*>(&frame_count), sizeof(int32_t));
173
174 // Read frames
175 for (int i = 0; i < frame_count; i++) {
176 editor.Frames.emplace_back();
177
178 int32_t tile_count = 0;
179 fs.read(reinterpret_cast<char*>(&tile_count), sizeof(int32_t));
180
181 for (int j = 0; j < tile_count; j++) {
182 uint16_t tid;
183 uint8_t tpal, tprior, tx, ty, tz;
184 bool tmx, tmy, tsize;
185
186 fs.read(reinterpret_cast<char*>(&tid), sizeof(uint16_t));
187 fs.read(reinterpret_cast<char*>(&tpal), sizeof(uint8_t));
188 fs.read(reinterpret_cast<char*>(&tmx), sizeof(bool));
189 fs.read(reinterpret_cast<char*>(&tmy), sizeof(bool));
190 fs.read(reinterpret_cast<char*>(&tprior), sizeof(uint8_t));
191 fs.read(reinterpret_cast<char*>(&tsize), sizeof(bool));
192 fs.read(reinterpret_cast<char*>(&tx), sizeof(uint8_t));
193 fs.read(reinterpret_cast<char*>(&ty), sizeof(uint8_t));
194 fs.read(reinterpret_cast<char*>(&tz), sizeof(uint8_t));
195
196 OamTile tile(tx, ty, tmx, tmy, tid, tpal, tsize, tprior);
197 tile.z = tz;
198 editor.Frames[i].Tiles.push_back(tile);
199 }
200 }
201
202 // Read 20 sprite boolean properties
203 fs.read(reinterpret_cast<char*>(&property_blockable.IsChecked), 1);
204 fs.read(reinterpret_cast<char*>(&property_canfall.IsChecked), 1);
205 fs.read(reinterpret_cast<char*>(&property_collisionlayer.IsChecked), 1);
206 fs.read(reinterpret_cast<char*>(&property_customdeath.IsChecked), 1);
207 fs.read(reinterpret_cast<char*>(&property_damagesound.IsChecked), 1);
208 fs.read(reinterpret_cast<char*>(&property_deflectarrows.IsChecked), 1);
209 fs.read(reinterpret_cast<char*>(&property_deflectprojectiles.IsChecked), 1);
210 fs.read(reinterpret_cast<char*>(&property_fast.IsChecked), 1);
211 fs.read(reinterpret_cast<char*>(&property_harmless.IsChecked), 1);
212 fs.read(reinterpret_cast<char*>(&property_impervious.IsChecked), 1);
213 fs.read(reinterpret_cast<char*>(&property_imperviousarrow.IsChecked), 1);
214 fs.read(reinterpret_cast<char*>(&property_imperviousmelee.IsChecked), 1);
215 fs.read(reinterpret_cast<char*>(&property_interaction.IsChecked), 1);
216 fs.read(reinterpret_cast<char*>(&property_isboss.IsChecked), 1);
217 fs.read(reinterpret_cast<char*>(&property_persist.IsChecked), 1);
218 fs.read(reinterpret_cast<char*>(&property_shadow.IsChecked), 1);
219 fs.read(reinterpret_cast<char*>(&property_smallshadow.IsChecked), 1);
220 fs.read(reinterpret_cast<char*>(&property_statis.IsChecked), 1);
221 fs.read(reinterpret_cast<char*>(&property_statue.IsChecked), 1);
222 fs.read(reinterpret_cast<char*>(&property_watersprite.IsChecked), 1);
223
224 // Read 6 sprite stat bytes
225 uint8_t prize, palette, oamnbr, hitbox, health, damage;
226 fs.read(reinterpret_cast<char*>(&prize), sizeof(uint8_t));
227 fs.read(reinterpret_cast<char*>(&palette), sizeof(uint8_t));
228 fs.read(reinterpret_cast<char*>(&oamnbr), sizeof(uint8_t));
229 fs.read(reinterpret_cast<char*>(&hitbox), sizeof(uint8_t));
230 fs.read(reinterpret_cast<char*>(&health), sizeof(uint8_t));
231 fs.read(reinterpret_cast<char*>(&damage), sizeof(uint8_t));
232
233 property_prize.Text = std::to_string(prize);
234 property_palette.Text = std::to_string(palette);
235 property_oamnbr.Text = std::to_string(oamnbr);
236 property_hitbox.Text = std::to_string(hitbox);
237 property_health.Text = std::to_string(health);
238 property_damage.Text = std::to_string(damage);
239
240 // Read optional sections (check if more data exists)
241 if (fs.peek() != EOF) {
244
245 int32_t routine_count = 0;
246 fs.read(reinterpret_cast<char*>(&routine_count), sizeof(int32_t));
247
248 for (int i = 0; i < routine_count; i++) {
249 std::string rname = ReadDotNetString(fs);
250 std::string rcode = ReadDotNetString(fs);
251 userRoutines.emplace_back(rname, rcode);
252 }
253 }
254
255 // Read optional sprite ID
256 if (fs.peek() != EOF) {
258 }
259
260 return absl::OkStatus();
261 }
262
266 absl::Status Save(const std::string& filename) {
267 if (animations.size() > 256 || editor.Frames.size() > 256 ||
268 userRoutines.size() > 1024)
269 return absl::InvalidArgumentError(
270 "ZSM asset exceeds supported authoring counts");
271 for (const auto& frame : editor.Frames)
272 if (frame.Tiles.size() > 128)
273 return absl::InvalidArgumentError("ZSM frame exceeds 128 tiles");
274 for (const auto& value :
277 if (value.empty())
278 continue;
279 int number = 0;
280 auto parsed =
281 std::from_chars(value.data(), value.data() + value.size(), number);
282 if (parsed.ec != std::errc{} ||
283 parsed.ptr != value.data() + value.size() || number < 0 ||
284 number > 255)
285 return absl::InvalidArgumentError("ZSM stats must be decimal bytes");
286 }
287 // Validate the complete serialized snapshot before opening the destination.
288 std::ostringstream fs(std::ios::binary);
289
290 // Write animation count
291 int32_t anim_count = static_cast<int32_t>(animations.size());
292 fs.write(reinterpret_cast<const char*>(&anim_count), sizeof(int32_t));
293
294 // Write animations
295 for (const auto& anim : animations) {
296 WriteDotNetString(fs, anim.frame_name);
297 fs.write(reinterpret_cast<const char*>(&anim.frame_start),
298 sizeof(uint8_t));
299 fs.write(reinterpret_cast<const char*>(&anim.frame_end), sizeof(uint8_t));
300 fs.write(reinterpret_cast<const char*>(&anim.frame_speed),
301 sizeof(uint8_t));
302 }
303
304 // Write frame count
305 int32_t frame_count = static_cast<int32_t>(editor.Frames.size());
306 fs.write(reinterpret_cast<const char*>(&frame_count), sizeof(int32_t));
307
308 // Write frames
309 for (const auto& frame : editor.Frames) {
310 int32_t tile_count = static_cast<int32_t>(frame.Tiles.size());
311 fs.write(reinterpret_cast<const char*>(&tile_count), sizeof(int32_t));
312
313 for (const auto& tile : frame.Tiles) {
314 fs.write(reinterpret_cast<const char*>(&tile.id), sizeof(uint16_t));
315 fs.write(reinterpret_cast<const char*>(&tile.palette), sizeof(uint8_t));
316 fs.write(reinterpret_cast<const char*>(&tile.mirror_x), sizeof(bool));
317 fs.write(reinterpret_cast<const char*>(&tile.mirror_y), sizeof(bool));
318 fs.write(reinterpret_cast<const char*>(&tile.priority),
319 sizeof(uint8_t));
320 fs.write(reinterpret_cast<const char*>(&tile.size), sizeof(bool));
321 fs.write(reinterpret_cast<const char*>(&tile.x), sizeof(uint8_t));
322 fs.write(reinterpret_cast<const char*>(&tile.y), sizeof(uint8_t));
323 fs.write(reinterpret_cast<const char*>(&tile.z), sizeof(uint8_t));
324 }
325 }
326
327 // Write 20 sprite boolean properties
328 fs.write(reinterpret_cast<const char*>(&property_blockable.IsChecked), 1);
329 fs.write(reinterpret_cast<const char*>(&property_canfall.IsChecked), 1);
330 fs.write(reinterpret_cast<const char*>(&property_collisionlayer.IsChecked),
331 1);
332 fs.write(reinterpret_cast<const char*>(&property_customdeath.IsChecked), 1);
333 fs.write(reinterpret_cast<const char*>(&property_damagesound.IsChecked), 1);
334 fs.write(reinterpret_cast<const char*>(&property_deflectarrows.IsChecked),
335 1);
336 fs.write(
337 reinterpret_cast<const char*>(&property_deflectprojectiles.IsChecked),
338 1);
339 fs.write(reinterpret_cast<const char*>(&property_fast.IsChecked), 1);
340 fs.write(reinterpret_cast<const char*>(&property_harmless.IsChecked), 1);
341 fs.write(reinterpret_cast<const char*>(&property_impervious.IsChecked), 1);
342 fs.write(reinterpret_cast<const char*>(&property_imperviousarrow.IsChecked),
343 1);
344 fs.write(reinterpret_cast<const char*>(&property_imperviousmelee.IsChecked),
345 1);
346 fs.write(reinterpret_cast<const char*>(&property_interaction.IsChecked), 1);
347 fs.write(reinterpret_cast<const char*>(&property_isboss.IsChecked), 1);
348 fs.write(reinterpret_cast<const char*>(&property_persist.IsChecked), 1);
349 fs.write(reinterpret_cast<const char*>(&property_shadow.IsChecked), 1);
350 fs.write(reinterpret_cast<const char*>(&property_smallshadow.IsChecked), 1);
351 fs.write(reinterpret_cast<const char*>(&property_statis.IsChecked), 1);
352 fs.write(reinterpret_cast<const char*>(&property_statue.IsChecked), 1);
353 fs.write(reinterpret_cast<const char*>(&property_watersprite.IsChecked), 1);
354
355 // Write 6 sprite stat bytes (parse from Text properties)
356 uint8_t prize = static_cast<uint8_t>(
357 std::stoi(property_prize.Text.empty() ? "0" : property_prize.Text));
358 uint8_t palette = static_cast<uint8_t>(
359 std::stoi(property_palette.Text.empty() ? "0" : property_palette.Text));
360 uint8_t oamnbr = static_cast<uint8_t>(
361 std::stoi(property_oamnbr.Text.empty() ? "0" : property_oamnbr.Text));
362 uint8_t hitbox = static_cast<uint8_t>(
363 std::stoi(property_hitbox.Text.empty() ? "0" : property_hitbox.Text));
364 uint8_t health = static_cast<uint8_t>(
365 std::stoi(property_health.Text.empty() ? "0" : property_health.Text));
366 uint8_t damage = static_cast<uint8_t>(
367 std::stoi(property_damage.Text.empty() ? "0" : property_damage.Text));
368
369 fs.write(reinterpret_cast<const char*>(&prize), sizeof(uint8_t));
370 fs.write(reinterpret_cast<const char*>(&palette), sizeof(uint8_t));
371 fs.write(reinterpret_cast<const char*>(&oamnbr), sizeof(uint8_t));
372 fs.write(reinterpret_cast<const char*>(&hitbox), sizeof(uint8_t));
373 fs.write(reinterpret_cast<const char*>(&health), sizeof(uint8_t));
374 fs.write(reinterpret_cast<const char*>(&damage), sizeof(uint8_t));
375
376 // Write sprite name
378
379 // Write user routines
380 int32_t routine_count = static_cast<int32_t>(userRoutines.size());
381 fs.write(reinterpret_cast<const char*>(&routine_count), sizeof(int32_t));
382 for (const auto& routine : userRoutines) {
383 WriteDotNetString(fs, routine.name);
384 WriteDotNetString(fs, routine.code);
385 }
386
387 // Write sprite ID
389
390 const std::string content = fs.str();
392 std::ofstream output(filename, std::ios::binary);
393 if (!output)
394 return absl::InternalError("Failed to open ZSM for writing: " + filename);
395 output.write(content.data(), content.size());
396 output.close();
397 if (!output)
398 return absl::DataLossError("Failed to write ZSM asset: " + filename);
399 return absl::OkStatus();
400 }
401
405 void Reset() {
406 sprName.clear();
407 animations.clear();
408 userRoutines.clear();
409 editor.Frames.clear();
410
411 // Reset boolean properties
419 property_fast.IsChecked = false;
432
433 // Reset text properties
434 property_sprname.Text.clear();
435 property_prize.Text = "0";
437 property_oamnbr.Text = "0";
438 property_hitbox.Text = "0";
439 property_health.Text = "0";
440 property_damage.Text = "0";
441 property_sprid.Text.clear();
442 }
443
444 std::string sprName;
445 std::vector<AnimationGroup> animations;
446 std::vector<UserRoutine> userRoutines;
448
449 // Boolean properties (20 total)
471
472 // Stat properties (6 total, stored as text)
479
481};
482
483} // namespace zsprite
484} // namespace editor
485} // namespace yaze
486
487#endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
absl::Status ValidateZsmBytes(std::string_view data)
std::string ReadDotNetString(std::istream &is)
Read a .NET BinaryReader format string (7-bit encoded length prefix).
Definition zsprite.h:31
void WriteDotNetString(std::ostream &os, const std::string &str)
Write a .NET BinaryWriter format string (7-bit encoded length prefix).
Definition zsprite.h:53
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< OamTile > Tiles
Definition zsprite.h:108
AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
Definition zsprite.h:98
std::vector< OamTile > Tiles
Definition zsprite.h:112
OamTile(uint8_t x, uint8_t y, bool mx, bool my, uint16_t id, uint8_t pal, bool s, uint8_t p)
Definition zsprite.h:74
std::vector< Frame > Frames
Definition zsprite.h:125
std::vector< UserRoutine > user_routines
Definition zsprite.h:126
UserRoutine(std::string n, std::string c)
Definition zsprite.h:117
std::vector< UserRoutine > userRoutines
Definition zsprite.h:446
void Reset()
Reset all sprite data to defaults.
Definition zsprite.h:405
SpriteProperty property_persist
Definition zsprite.h:464
absl::Status Save(const std::string &filename)
Save a ZSM file to disk.
Definition zsprite.h:266
SpriteProperty property_hitbox
Definition zsprite.h:476
SpriteProperty property_isboss
Definition zsprite.h:463
SpriteProperty property_impervious
Definition zsprite.h:459
SpriteProperty property_collisionlayer
Definition zsprite.h:452
SpriteProperty property_prize
Definition zsprite.h:473
SpriteProperty property_imperviousarrow
Definition zsprite.h:460
SpriteProperty property_smallshadow
Definition zsprite.h:466
SpriteProperty property_blockable
Definition zsprite.h:450
SpriteProperty property_shadow
Definition zsprite.h:465
absl::Status Load(const std::string &filename)
Load a ZSM file from disk.
Definition zsprite.h:139
SpriteProperty property_deflectarrows
Definition zsprite.h:455
SpriteProperty property_customdeath
Definition zsprite.h:453
SpriteProperty property_imperviousmelee
Definition zsprite.h:461
SpriteProperty property_fast
Definition zsprite.h:457
SpriteProperty property_oamnbr
Definition zsprite.h:475
SpriteProperty property_interaction
Definition zsprite.h:462
SpriteProperty property_watersprite
Definition zsprite.h:469
SpriteProperty property_statue
Definition zsprite.h:468
SpriteProperty property_sprname
Definition zsprite.h:470
SpriteProperty property_damagesound
Definition zsprite.h:454
SpriteProperty property_damage
Definition zsprite.h:478
std::vector< AnimationGroup > animations
Definition zsprite.h:445
SpriteProperty property_palette
Definition zsprite.h:474
SpriteProperty property_harmless
Definition zsprite.h:458
SpriteProperty property_health
Definition zsprite.h:477
SpriteProperty property_canfall
Definition zsprite.h:451
SpriteProperty property_statis
Definition zsprite.h:467
SpriteProperty property_deflectprojectiles
Definition zsprite.h:456
SpriteProperty property_sprid
Definition zsprite.h:480