yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom.cc
Go to the documentation of this file.
1#include "rom.h"
2
3#include <algorithm>
4#include <chrono>
5#include <cstddef>
6#include <cstdint>
7#include <cstring>
8#include <ctime>
9#include <filesystem>
10#include <fstream>
11#include <iostream>
12#include <new>
13#include <string>
14#include <system_error>
15#include <vector>
16
17#include "absl/status/status.h"
18#include "absl/status/statusor.h"
19#include "absl/strings/str_cat.h"
20#include "absl/strings/str_format.h"
21#include "absl/strings/string_view.h"
24#include "rom/write_fence.h"
25#include "util/hex.h"
26#include "util/log.h"
27#include "util/macro.h"
28
29#ifdef __EMSCRIPTEN__
30#include <emscripten.h>
32#endif
33
34#if !defined(__EMSCRIPTEN__)
35#if defined(_WIN32)
36#include <windows.h>
37#else
38#include <fcntl.h>
39#include <unistd.h>
40#endif
41#endif
42
43namespace yaze {
44
45namespace {
46
47// ============================================================================
48// ROM Structure Constants
49// ============================================================================
50
52constexpr size_t kBaseRomSize = 1048576;
53
55constexpr size_t kHeaderSize = 0x200; // 512 bytes
56
57// ============================================================================
58// SMC Header Detection and Removal
59// ============================================================================
60
61void MaybeStripSmcHeader(std::vector<uint8_t>& rom_data, unsigned long& size) {
62 if (size % kBaseRomSize == kHeaderSize && size >= kHeaderSize &&
63 rom_data.size() >= kHeaderSize) {
64 rom_data.erase(rom_data.begin(), rom_data.begin() + kHeaderSize);
65 size -= kHeaderSize;
66 LOG_INFO("Rom", "Stripped SMC header from ROM (new size: %lu)", size);
67 }
68}
69
70std::string MakeSafeTimestamp(std::time_t now_c) {
71 std::string timestamp = std::ctime(&now_c);
72 timestamp.erase(std::remove(timestamp.begin(), timestamp.end(), '\n'),
73 timestamp.end());
74 std::replace(timestamp.begin(), timestamp.end(), ' ', '_');
75
76 // Keep backup/save-new filenames valid across platforms (especially
77 // Windows, where ':' and several other characters are not allowed).
78 for (char& ch : timestamp) {
79 switch (ch) {
80 case '<':
81 case '>':
82 case ':':
83 case '"':
84 case '/':
85 case '\\':
86 case '|':
87 case '?':
88 case '*':
89 ch = '-';
90 break;
91 default:
92 break;
93 }
94 }
95 return timestamp;
96}
97
98absl::StatusOr<std::filesystem::path> GetAvailableBackupPath(
99 const std::filesystem::path& requested_path) {
100 std::filesystem::path candidate = requested_path;
101 for (int suffix = 1; suffix <= 1000; ++suffix) {
102 std::error_code exists_ec;
103 const bool exists = std::filesystem::exists(candidate, exists_ec);
104 if (exists_ec) {
105 return absl::InternalError(absl::StrCat(
106 "Could not inspect required ROM backup path: ", candidate.string(),
107 ": ", exists_ec.message()));
108 }
109 if (!exists) {
110 return candidate;
111 }
112 candidate = requested_path.string() + "_" + std::to_string(suffix);
113 }
114
115 return absl::ResourceExhaustedError(
116 "Could not allocate a unique required ROM backup path");
117}
118
119#if !defined(__EMSCRIPTEN__)
120void BestEffortFsyncFile(const std::filesystem::path& path);
121void BestEffortFsyncParentDir(const std::filesystem::path& file_path);
122#endif
123
125 const std::filesystem::path& source_path,
126 const std::filesystem::path& requested_backup_path) {
127 auto backup_path_or = GetAvailableBackupPath(requested_backup_path);
128 if (!backup_path_or.ok()) {
129 return backup_path_or.status();
130 }
131
132 const std::filesystem::path backup_path = *backup_path_or;
133 std::filesystem::path temp_path = backup_path;
134 temp_path += ".tmp";
135
136 std::error_code copy_ec;
137 const bool copied = std::filesystem::copy_file(
138 source_path, temp_path, std::filesystem::copy_options::none, copy_ec);
139 if (!copied || copy_ec) {
140 std::error_code cleanup_ec;
141 std::filesystem::remove(temp_path, cleanup_ec);
142 return absl::FailedPreconditionError(absl::StrCat(
143 "Could not create required ROM backup: ", source_path.string(), " -> ",
144 backup_path.string(), ": ",
145 copy_ec ? copy_ec.message() : "copy did not complete"));
146 }
147
148#if !defined(__EMSCRIPTEN__)
149 BestEffortFsyncFile(temp_path);
150#endif
151
152 std::error_code rename_ec;
153 std::filesystem::rename(temp_path, backup_path, rename_ec);
154 if (rename_ec) {
155 std::error_code cleanup_ec;
156 std::filesystem::remove(temp_path, cleanup_ec);
157 return absl::FailedPreconditionError(absl::StrCat(
158 "Could not finalize required ROM backup: ", backup_path.string(), ": ",
159 rename_ec.message()));
160 }
161
162#if !defined(__EMSCRIPTEN__)
163 BestEffortFsyncParentDir(backup_path);
164#endif
165
166 return absl::OkStatus();
167}
168
169#ifdef __EMSCRIPTEN__
170inline void MaybeBroadcastChange(uint32_t offset,
171 const std::vector<uint8_t>& old_bytes,
172 const std::vector<uint8_t>& new_bytes) {
173 if (new_bytes.empty())
174 return;
175 auto& collab = app::platform::GetWasmCollaborationInstance();
176 if (!collab.IsConnected() || collab.IsApplyingRemoteChange()) {
177 return;
178 }
179 (void)collab.BroadcastChange(offset, old_bytes, new_bytes);
180}
181#endif
182
183#if !defined(__EMSCRIPTEN__)
184void BestEffortFsyncFile(const std::filesystem::path& path) {
185#if defined(_WIN32)
186 // FlushFileBuffers requires GENERIC_WRITE access.
187 HANDLE handle =
188 CreateFileW(path.wstring().c_str(), GENERIC_WRITE,
189 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
190 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
191 if (handle == INVALID_HANDLE_VALUE) {
192 return;
193 }
194 (void)FlushFileBuffers(handle);
195 (void)CloseHandle(handle);
196#else
197 int fd = open(path.c_str(), O_RDONLY);
198 if (fd < 0) {
199 return;
200 }
201 (void)fsync(fd);
202 (void)close(fd);
203#endif
204}
205
206void BestEffortFsyncParentDir(const std::filesystem::path& file_path) {
207#if defined(_WIN32)
208 (void)file_path;
209 // Best-effort only; Windows directory fsync is not portable here.
210#else
211 std::filesystem::path dir_path = file_path.parent_path();
212 if (dir_path.empty()) {
213 dir_path = ".";
214 }
215 int fd = open(dir_path.c_str(), O_RDONLY);
216 if (fd < 0) {
217 return;
218 }
219 (void)fsync(fd);
220 (void)close(fd);
221#endif
222}
223#endif // !defined(__EMSCRIPTEN__)
224
225} // namespace
226
227Rom::Rom(const Rom& other)
228 : size_(other.size_),
229 title_(other.title_),
230 filename_(other.filename_),
231 short_name_(other.short_name_),
232 rom_data_(other.rom_data_),
233 resource_label_manager_(other.resource_label_manager_),
234 dirty_(other.dirty_),
235 object_tile_revision_(other.object_tile_revision_) {
236 // Write fences are non-owning, instance-local transaction state. A new Rom
237 // must never inherit pointers owned by active scopes on `other`.
238}
239
240Rom& Rom::operator=(const Rom& other) {
241 if (this == &other) {
242 return *this;
243 }
244
246 size_ = other.size_;
247 title_ = other.title_;
248 filename_ = other.filename_;
249 short_name_ = other.short_name_;
250 rom_data_ = other.rom_data_;
251 resource_label_manager_ = other.resource_label_manager_;
252 dirty_ = other.dirty_;
253
254 // Write fences are non-owning, instance-local transaction state. Preserve
255 // this Rom's active scopes rather than importing pointers owned by `other`.
256
258 std::max(previous_revision, other.object_tile_revision_);
260 return *this;
261}
262
264 // Rom historically has copy-on-rvalue assignment semantics. Keep the source
265 // intact while ensuring in-place session replacement uses the same strictly
266 // monotonic destination generation as lvalue assignment.
267 return operator=(static_cast<const Rom&>(other));
268}
269
270absl::Status Rom::LoadFromFile(const std::string& filename,
271 const LoadOptions& options) {
272 if (filename.empty()) {
273 return absl::InvalidArgumentError(
274 "Could not load ROM: parameter `filename` is empty.");
275 }
276
277#ifdef __EMSCRIPTEN__
279 std::ifstream test_file(filename_, std::ios::binary);
280 if (!test_file.is_open()) {
281 return absl::NotFoundError(absl::StrCat(
282 "ROM file does not exist or cannot be opened: ", filename_));
283 }
284 test_file.seekg(0, std::ios::end);
285 size_ = test_file.tellg();
286 test_file.close();
287
288 if (size_ < 32768) {
289 return absl::InvalidArgumentError(absl::StrFormat(
290 "ROM file too small (%zu bytes), minimum is 32KB", size_));
291 }
292#else
293 if (!std::filesystem::exists(filename)) {
294 return absl::NotFoundError(
295 absl::StrCat("ROM file does not exist: ", filename));
296 }
297 filename_ = std::filesystem::absolute(filename).string();
298#endif
299 short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1);
300
301 std::ifstream file(filename_, std::ios::binary);
302 if (!file.is_open()) {
303 return absl::NotFoundError(
304 absl::StrCat("Could not open ROM file: ", filename_));
305 }
306
307#ifndef __EMSCRIPTEN__
308 try {
309 size_ = std::filesystem::file_size(filename_);
310 if (size_ < 32768) {
311 return absl::InvalidArgumentError(absl::StrFormat(
312 "ROM file too small (%zu bytes), minimum is 32KB", size_));
313 }
314 } catch (...) {
315 file.seekg(0, std::ios::end);
316 size_ = file.tellg();
317 }
318#endif
319
320 // ALttP ROMs are <= 4MB (with an SMC header ~4MB+512); reject anything far
321 // larger so a mis-selected file cannot force a huge up-front allocation.
322 constexpr size_t kMaxRomSize = 16 * 1024 * 1024; // 16 MB
323 if (size_ > kMaxRomSize) {
324 return absl::InvalidArgumentError(absl::StrFormat(
325 "ROM file too large (%zu bytes), maximum is 16MB", size_));
326 }
327
328 try {
329 rom_data_.resize(size_);
330 file.seekg(0, std::ios::beg);
331 file.read(reinterpret_cast<char*>(rom_data_.data()), size_);
332 } catch (const std::bad_alloc& e) {
333 return absl::ResourceExhaustedError(absl::StrFormat(
334 "Failed to allocate memory for ROM (%zu bytes)", size_));
335 }
336
337 file.close();
338
339 if (options.strip_header) {
340 MaybeStripSmcHeader(rom_data_, size_);
341 }
342 size_ = rom_data_.size();
343
344 if (options.load_resource_labels) {
345 resource_label_manager_.LoadLabels(absl::StrFormat("%s.labels", filename));
346 }
347
348 // Parse SNES Header for Title
349 if (rom_data_.size() >= 0x8000) {
350 // Check LoROM (0x7FC0) vs HiROM (0xFFC0)
351 // Simple heuristic: Z3 is LoROM
352 size_t header_offset = 0x7FC0;
353 if (rom_data_.size() >= 0x10000) {
354 // Compute checksums to verify?
355 // For now default to LoROM
356 }
357
358 if (header_offset + 21 <= rom_data_.size()) {
359 char buffer[22] = {0};
360 for (int i = 0; i < 21; ++i) {
362 buffer[i] = (c >= 32 && c <= 126) ? c : ' ';
363 }
364 title_ = std::string(buffer);
365 // Trim trailing spaces safely
366 auto last_non_space = title_.find_last_not_of(' ');
367 if (last_non_space == std::string::npos) {
368 title_.clear();
369 } else {
370 title_.erase(last_non_space + 1);
371 }
372 }
373 }
374
376 return absl::OkStatus();
377}
378
379absl::Status Rom::LoadFromData(const std::vector<uint8_t>& data,
380 const LoadOptions& options) {
381 if (data.empty()) {
382 return absl::InvalidArgumentError(
383 "Could not load ROM: parameter `data` is empty.");
384 }
385 rom_data_ = data;
386 size_ = data.size();
387
388 if (options.strip_header) {
389 MaybeStripSmcHeader(rom_data_, size_);
390 }
391 size_ = rom_data_.size();
392
393 // Parse SNES Header for Title
394 if (rom_data_.size() >= 0x8000) {
395 size_t header_offset = 0x7FC0;
396 if (header_offset + 21 <= rom_data_.size()) {
397 char buffer[22] = {0};
398 for (int i = 0; i < 21; ++i) {
400 buffer[i] = (c >= 32 && c <= 126) ? c : ' ';
401 }
402 title_ = std::string(buffer);
403 auto last_non_space = title_.find_last_not_of(' ');
404 if (last_non_space == std::string::npos) {
405 title_.clear();
406 } else {
407 title_.erase(last_non_space + 1);
408 }
409 }
410 }
411
413 return absl::OkStatus();
414}
415
416absl::Status Rom::SaveToFile(const SaveSettings& settings) {
417 if (rom_data_.empty()) {
418 return absl::InternalError("ROM data is empty.");
419 }
420
421 std::string filename = settings.filename;
422 if (filename.empty()) {
424 }
425
426 // Backup modes must reason about the actual destination, including the
427 // timestamped path selected by save_new.
428 if (settings.save_new) {
429 auto now = std::chrono::system_clock::now();
430 auto now_c = std::chrono::system_clock::to_time_t(now);
431 auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
432 filename =
433 absl::StrCat(filename_no_ext, "_", MakeSafeTimestamp(now_c), ".sfc");
434 }
435
436 if (settings.require_backup) {
437 const std::filesystem::path target_path(filename);
438 std::error_code exists_ec;
439 const bool target_exists = std::filesystem::exists(target_path, exists_ec);
440 if (exists_ec) {
441 return absl::FailedPreconditionError(absl::StrCat(
442 "Could not inspect required ROM backup target: ", filename, ": ",
443 exists_ec.message()));
444 }
445
446 // A strict backup protects the file that is about to be replaced, not the
447 // ROM's original load path. A new target has no previous bytes to protect.
448 if (target_exists) {
449 auto now = std::chrono::system_clock::now();
450 auto now_c = std::chrono::system_clock::to_time_t(now);
451 std::string backup_filename =
452 absl::StrCat(filename, "_backup_", MakeSafeTimestamp(now_c));
453 RETURN_IF_ERROR(CreateRequiredBackup(target_path, backup_filename));
454 }
455 } else if (settings.backup) {
456 try {
457 const std::filesystem::path target_path(filename);
458 // Best-effort backups protect the file about to be replaced. A new
459 // destination has no previous bytes to preserve.
460 if (std::filesystem::exists(target_path)) {
461 auto now = std::chrono::system_clock::now();
462 auto now_c = std::chrono::system_clock::to_time_t(now);
463 std::string backup_filename =
464 absl::StrCat(filename, "_backup_", MakeSafeTimestamp(now_c));
465 std::filesystem::copy(
466 target_path, backup_filename,
467 std::filesystem::copy_options::overwrite_existing);
468 }
469 } catch (const std::filesystem::filesystem_error& e) {
470 LOG_WARN("Rom", "Could not create backup: %s", e.what());
471 }
472 }
473
474 // Save stability: write to a temp file in the same directory and rename into
475 // place. If we crash mid-write, the original ROM stays intact.
476 const std::filesystem::path target_path(filename);
477 std::filesystem::path temp_path = target_path;
478 temp_path += ".tmp";
479
480 std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
481 if (!file) {
482 return absl::InternalError(absl::StrCat(
483 "Could not open temp ROM file for writing: ", temp_path.string()));
484 }
485
486 file.write(reinterpret_cast<const char*>(rom_data_.data()), rom_data_.size());
487 file.flush();
488 if (!file) {
489 file.close();
490 std::error_code rm_ec;
491 std::filesystem::remove(temp_path, rm_ec);
492 return absl::InternalError(
493 absl::StrCat("Error while writing ROM file: ", temp_path.string()));
494 }
495
496 file.close();
497
498#if !defined(__EMSCRIPTEN__)
499 // Best-effort fsync so temp file contents are durable before rename.
500 BestEffortFsyncFile(temp_path);
501#endif
502
503 std::error_code rename_ec;
504 std::filesystem::rename(temp_path, target_path, rename_ec);
505#if defined(_WIN32)
506 // Windows may reject std::filesystem::rename when the destination exists.
507 // Replace it without deleting the original first, so a failed replacement
508 // does not leave the target missing.
509 if (rename_ec) {
510 if (MoveFileExW(temp_path.wstring().c_str(), target_path.wstring().c_str(),
512 rename_ec.clear();
513 } else {
514 rename_ec = std::error_code(static_cast<int>(GetLastError()),
515 std::system_category());
516 }
517 }
518#endif
519 if (rename_ec) {
520 std::error_code rm_ec;
521 std::filesystem::remove(temp_path, rm_ec);
522 return absl::InternalError(absl::StrCat(
523 "Failed to move temp ROM into place: ", rename_ec.message()));
524 }
525
526#if !defined(__EMSCRIPTEN__)
527 // Best-effort fsync the parent dir so the rename is durable.
528 BestEffortFsyncParentDir(target_path);
529#endif
530
531 dirty_ = false;
532 return absl::OkStatus();
533}
534
536 if (fence == nullptr) {
537 return;
538 }
539 write_fence_stack_.push_back(fence);
540}
541
543 if (fence == nullptr) {
544 return;
545 }
546 if (!write_fence_stack_.empty() && write_fence_stack_.back() == fence) {
547 write_fence_stack_.pop_back();
548 return;
549 }
550
551 // Defensive: avoid leaving a stale fence active if call sites mismatch.
552 for (auto it = write_fence_stack_.rbegin(); it != write_fence_stack_.rend();
553 ++it) {
554 if (*it == fence) {
555 write_fence_stack_.erase(std::next(it).base());
556 LOG_WARN("Rom", "Popped non-top write fence (mismatched scope)");
557 return;
558 }
559 }
560 LOG_WARN("Rom", "PopWriteFence called for unknown fence");
561}
562
563absl::StatusOr<uint8_t> Rom::ReadByte(int offset) const {
564 if (offset < 0 || offset >= static_cast<int>(rom_data_.size())) {
565 return absl::OutOfRangeError(absl::StrFormat(
566 "Offset %d out of range (size: %d)", offset, rom_data_.size()));
567 }
568 return rom_data_[offset];
569}
570
571absl::StatusOr<uint16_t> Rom::ReadWord(int offset) const {
572 if (offset < 0 || offset + 1 >= static_cast<int>(rom_data_.size())) {
573 return absl::OutOfRangeError("Offset out of range");
574 }
575 return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8));
576}
577
578absl::StatusOr<uint32_t> Rom::ReadLong(int offset) const {
579 if (offset < 0 || offset + 2 >= static_cast<int>(rom_data_.size())) {
580 return absl::OutOfRangeError("Offset out of range");
581 }
582 return (uint32_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8) |
583 (rom_data_[offset + 2] << 16));
584}
585
586absl::StatusOr<std::vector<uint8_t>> Rom::ReadByteVector(
587 uint32_t offset, uint32_t length) const {
588 if (offset + length > static_cast<uint32_t>(rom_data_.size())) {
589 return absl::OutOfRangeError("Offset and length out of range");
590 }
591 std::vector<uint8_t> result;
592 result.reserve(length);
593 for (uint32_t i = offset; i < offset + length; i++) {
594 result.push_back(rom_data_[i]);
595 }
596 return result;
597}
598
599absl::StatusOr<gfx::Tile16> Rom::ReadTile16(uint32_t tile16_id,
600 uint32_t tile16_ptr) {
601 // Skip 8 bytes per tile.
602 auto tpos = tile16_ptr + (tile16_id * 0x08);
603 gfx::Tile16 tile16 = {};
606 tpos += 2;
609 tpos += 2;
612 tpos += 2;
615 return tile16;
616}
617
618absl::Status Rom::WriteTile16(int tile16_id, uint32_t tile16_ptr,
619 const gfx::Tile16& tile) {
620 auto tpos = tile16_ptr + (tile16_id * 0x08);
622 tpos += 2;
624 tpos += 2;
626 tpos += 2;
628 return absl::OkStatus();
629}
630
631absl::Status Rom::WriteByte(int addr, uint8_t value) {
632 if (addr < 0 || addr >= static_cast<int>(rom_data_.size())) {
633 return absl::OutOfRangeError("Address out of range");
634 }
635 for (auto* fence : write_fence_stack_) {
636 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr), 1, "WriteByte"));
637 }
638 const uint8_t old_val = rom_data_[addr];
639 rom_data_[addr] = value;
640 dirty_ = true;
641#ifdef __EMSCRIPTEN__
642 MaybeBroadcastChange(addr, {old_val}, {value});
643#endif
644 for (auto* fence : write_fence_stack_) {
645 fence->RecordWrite(static_cast<uint32_t>(addr), 1);
646 }
647 return absl::OkStatus();
648}
649
650absl::Status Rom::WriteWord(int addr, uint16_t value) {
651 if (addr < 0 || addr + 1 >= static_cast<int>(rom_data_.size())) {
652 return absl::OutOfRangeError("Address out of range");
653 }
654 for (auto* fence : write_fence_stack_) {
655 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr), 2, "WriteWord"));
656 }
657 const uint8_t old0 = rom_data_[addr];
658 const uint8_t old1 = rom_data_[addr + 1];
659 rom_data_[addr] = (uint8_t)(value & 0xFF);
660 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
661 dirty_ = true;
662#ifdef __EMSCRIPTEN__
664 {static_cast<uint8_t>(value & 0xFF),
665 static_cast<uint8_t>((value >> 8) & 0xFF)});
666#endif
667 for (auto* fence : write_fence_stack_) {
668 fence->RecordWrite(static_cast<uint32_t>(addr), 2);
669 }
670 return absl::OkStatus();
671}
672
673absl::Status Rom::WriteShort(int addr, uint16_t value) {
674 return WriteWord(addr, value);
675}
676
677absl::Status Rom::WriteLong(uint32_t addr, uint32_t value) {
678 if (addr + 2 >= static_cast<uint32_t>(rom_data_.size())) {
679 return absl::OutOfRangeError("Address out of range");
680 }
681 for (auto* fence : write_fence_stack_) {
682 RETURN_IF_ERROR(fence->Check(addr, 3, "WriteLong"));
683 }
684 const uint8_t old0 = rom_data_[addr];
685 const uint8_t old1 = rom_data_[addr + 1];
686 const uint8_t old2 = rom_data_[addr + 2];
687 rom_data_[addr] = (uint8_t)(value & 0xFF);
688 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
689 rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF);
690 dirty_ = true;
691#ifdef __EMSCRIPTEN__
693 {static_cast<uint8_t>(value & 0xFF),
694 static_cast<uint8_t>((value >> 8) & 0xFF),
695 static_cast<uint8_t>((value >> 16) & 0xFF)});
696#endif
697 for (auto* fence : write_fence_stack_) {
698 fence->RecordWrite(addr, 3);
699 }
700 return absl::OkStatus();
701}
702
703absl::Status Rom::WriteVector(int addr, std::vector<uint8_t> data) {
704 if (addr < 0) {
705 return absl::OutOfRangeError("Address out of range");
706 }
707 if (addr + static_cast<int>(data.size()) >
708 static_cast<int>(rom_data_.size())) {
709 return absl::OutOfRangeError("Address out of range");
710 }
711 for (auto* fence : write_fence_stack_) {
712 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr),
713 static_cast<uint32_t>(data.size()),
714 "WriteVector"));
715 }
716 std::vector<uint8_t> old_data;
717 old_data.reserve(data.size());
718 for (int i = 0; i < static_cast<int>(data.size()); i++) {
719 old_data.push_back(rom_data_[addr + i]);
720 rom_data_[addr + i] = data[i];
721 }
722 dirty_ = true;
723#ifdef __EMSCRIPTEN__
724 MaybeBroadcastChange(addr, old_data, data);
725#endif
726 for (auto* fence : write_fence_stack_) {
727 fence->RecordWrite(static_cast<uint32_t>(addr),
728 static_cast<uint32_t>(data.size()));
729 }
730 return absl::OkStatus();
731}
732
733absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor& color) {
734 uint16_t bgr = ((color.snes() >> 10) & 0x1F) | ((color.snes() & 0x1F) << 10) |
735 (color.snes() & 0x7C00);
736 return WriteWord(address, bgr);
737}
738
739absl::Status Rom::WriteHelper(const WriteAction& action) {
740 if (std::holds_alternative<uint8_t>(action.value)) {
741 return WriteByte(action.address, std::get<uint8_t>(action.value));
742 } else if (std::holds_alternative<uint16_t>(action.value) ||
743 std::holds_alternative<short>(action.value)) {
744 return WriteShort(action.address, std::get<uint16_t>(action.value));
745 } else if (std::holds_alternative<std::vector<uint8_t>>(action.value)) {
746 return WriteVector(action.address,
747 std::get<std::vector<uint8_t>>(action.value));
748 } else if (std::holds_alternative<gfx::SnesColor>(action.value)) {
749 return WriteColor(action.address, std::get<gfx::SnesColor>(action.value));
750 }
751 return absl::InvalidArgumentError("Invalid write argument type");
752}
753
754} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:586
void PushWriteFence(rom::WriteFence *fence)
Definition rom.cc:535
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:270
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
Definition rom.cc:599
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:733
auto filename() const
Definition rom.h:175
void PopWriteFence(rom::WriteFence *fence)
Definition rom.cc:542
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:631
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:563
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
Definition rom.cc:618
const auto & vector() const
Definition rom.h:173
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:703
std::string title_
Definition rom.h:198
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:416
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:571
std::vector< uint8_t > rom_data_
Definition rom.h:207
Rom()=default
auto data() const
Definition rom.h:169
std::vector< rom::WriteFence * > write_fence_stack_
Definition rom.h:219
Rom & operator=(const Rom &other)
Definition rom.cc:240
bool dirty_
Definition rom.h:213
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:379
void AdvanceObjectTileRevision()
Definition rom.h:165
std::string filename_
Definition rom.h:201
unsigned long size_
Definition rom.h:195
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:673
project::ResourceLabelManager resource_label_manager_
Definition rom.h:210
std::string short_name_
Definition rom.h:204
uint64_t object_tile_revision_
Definition rom.h:216
absl::Status WriteWord(int addr, uint16_t value)
Definition rom.cc:650
virtual absl::Status WriteHelper(const WriteAction &action)
Definition rom.cc:739
absl::Status WriteLong(uint32_t addr, uint32_t value)
Definition rom.cc:677
absl::StatusOr< uint32_t > ReadLong(int offset) const
Definition rom.cc:578
SNES Color container.
Definition snes_color.h:110
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::string MakeSafeTimestamp(std::time_t now_c)
Definition rom.cc:70
absl::Status CreateRequiredBackup(const std::filesystem::path &source_path, const std::filesystem::path &requested_backup_path)
Definition rom.cc:124
void BestEffortFsyncFile(const std::filesystem::path &path)
Definition rom.cc:184
void BestEffortFsyncParentDir(const std::filesystem::path &file_path)
Definition rom.cc:206
void MaybeStripSmcHeader(std::vector< uint8_t > &rom_data, unsigned long &size)
Definition rom.cc:61
constexpr size_t kHeaderSize
Size of the optional SMC/SFC copier header that some ROM dumps include.
Definition rom.cc:55
constexpr size_t kBaseRomSize
Standard SNES ROM size for The Legend of Zelda: A Link to the Past (1MB)
Definition rom.cc:52
absl::StatusOr< std::filesystem::path > GetAvailableBackupPath(const std::filesystem::path &requested_path)
Definition rom.cc:98
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:361
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
bool load_resource_labels
Definition rom.h:43
std::string filename
Definition rom.h:33
ValueType value
Definition rom.h:123
bool LoadLabels(const std::string &filename)
Definition project.cc:2256