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
227absl::Status Rom::LoadFromFile(const std::string& filename,
228 const LoadOptions& options) {
229 if (filename.empty()) {
230 return absl::InvalidArgumentError(
231 "Could not load ROM: parameter `filename` is empty.");
232 }
233
234#ifdef __EMSCRIPTEN__
236 std::ifstream test_file(filename_, std::ios::binary);
237 if (!test_file.is_open()) {
238 return absl::NotFoundError(absl::StrCat(
239 "ROM file does not exist or cannot be opened: ", filename_));
240 }
241 test_file.seekg(0, std::ios::end);
242 size_ = test_file.tellg();
243 test_file.close();
244
245 if (size_ < 32768) {
246 return absl::InvalidArgumentError(absl::StrFormat(
247 "ROM file too small (%zu bytes), minimum is 32KB", size_));
248 }
249#else
250 if (!std::filesystem::exists(filename)) {
251 return absl::NotFoundError(
252 absl::StrCat("ROM file does not exist: ", filename));
253 }
254 filename_ = std::filesystem::absolute(filename).string();
255#endif
256 short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1);
257
258 std::ifstream file(filename_, std::ios::binary);
259 if (!file.is_open()) {
260 return absl::NotFoundError(
261 absl::StrCat("Could not open ROM file: ", filename_));
262 }
263
264#ifndef __EMSCRIPTEN__
265 try {
266 size_ = std::filesystem::file_size(filename_);
267 if (size_ < 32768) {
268 return absl::InvalidArgumentError(absl::StrFormat(
269 "ROM file too small (%zu bytes), minimum is 32KB", size_));
270 }
271 } catch (...) {
272 file.seekg(0, std::ios::end);
273 size_ = file.tellg();
274 }
275#endif
276
277 // ALttP ROMs are <= 4MB (with an SMC header ~4MB+512); reject anything far
278 // larger so a mis-selected file cannot force a huge up-front allocation.
279 constexpr size_t kMaxRomSize = 16 * 1024 * 1024; // 16 MB
280 if (size_ > kMaxRomSize) {
281 return absl::InvalidArgumentError(absl::StrFormat(
282 "ROM file too large (%zu bytes), maximum is 16MB", size_));
283 }
284
285 try {
286 rom_data_.resize(size_);
287 file.seekg(0, std::ios::beg);
288 file.read(reinterpret_cast<char*>(rom_data_.data()), size_);
289 } catch (const std::bad_alloc& e) {
290 return absl::ResourceExhaustedError(absl::StrFormat(
291 "Failed to allocate memory for ROM (%zu bytes)", size_));
292 }
293
294 file.close();
295
296 if (options.strip_header) {
297 MaybeStripSmcHeader(rom_data_, size_);
298 }
299 size_ = rom_data_.size();
300
301 if (options.load_resource_labels) {
302 resource_label_manager_.LoadLabels(absl::StrFormat("%s.labels", filename));
303 }
304
305 // Parse SNES Header for Title
306 if (rom_data_.size() >= 0x8000) {
307 // Check LoROM (0x7FC0) vs HiROM (0xFFC0)
308 // Simple heuristic: Z3 is LoROM
309 size_t header_offset = 0x7FC0;
310 if (rom_data_.size() >= 0x10000) {
311 // Compute checksums to verify?
312 // For now default to LoROM
313 }
314
315 if (header_offset + 21 <= rom_data_.size()) {
316 char buffer[22] = {0};
317 for (int i = 0; i < 21; ++i) {
319 buffer[i] = (c >= 32 && c <= 126) ? c : ' ';
320 }
321 title_ = std::string(buffer);
322 // Trim trailing spaces safely
323 auto last_non_space = title_.find_last_not_of(' ');
324 if (last_non_space == std::string::npos) {
325 title_.clear();
326 } else {
327 title_.erase(last_non_space + 1);
328 }
329 }
330 }
331
332 return absl::OkStatus();
333}
334
335absl::Status Rom::LoadFromData(const std::vector<uint8_t>& data,
336 const LoadOptions& options) {
337 if (data.empty()) {
338 return absl::InvalidArgumentError(
339 "Could not load ROM: parameter `data` is empty.");
340 }
341 rom_data_ = data;
342 size_ = data.size();
343
344 if (options.strip_header) {
345 MaybeStripSmcHeader(rom_data_, size_);
346 }
347 size_ = rom_data_.size();
348
349 // Parse SNES Header for Title
350 if (rom_data_.size() >= 0x8000) {
351 size_t header_offset = 0x7FC0;
352 if (header_offset + 21 <= rom_data_.size()) {
353 char buffer[22] = {0};
354 for (int i = 0; i < 21; ++i) {
356 buffer[i] = (c >= 32 && c <= 126) ? c : ' ';
357 }
358 title_ = std::string(buffer);
359 auto last_non_space = title_.find_last_not_of(' ');
360 if (last_non_space == std::string::npos) {
361 title_.clear();
362 } else {
363 title_.erase(last_non_space + 1);
364 }
365 }
366 }
367
368 return absl::OkStatus();
369}
370
371absl::Status Rom::SaveToFile(const SaveSettings& settings) {
372 if (rom_data_.empty()) {
373 return absl::InternalError("ROM data is empty.");
374 }
375
376 std::string filename = settings.filename;
377 if (filename.empty()) {
379 }
380
381 // Backup modes must reason about the actual destination, including the
382 // timestamped path selected by save_new.
383 if (settings.save_new) {
384 auto now = std::chrono::system_clock::now();
385 auto now_c = std::chrono::system_clock::to_time_t(now);
386 auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
387 filename =
388 absl::StrCat(filename_no_ext, "_", MakeSafeTimestamp(now_c), ".sfc");
389 }
390
391 if (settings.require_backup) {
392 const std::filesystem::path target_path(filename);
393 std::error_code exists_ec;
394 const bool target_exists = std::filesystem::exists(target_path, exists_ec);
395 if (exists_ec) {
396 return absl::FailedPreconditionError(absl::StrCat(
397 "Could not inspect required ROM backup target: ", filename, ": ",
398 exists_ec.message()));
399 }
400
401 // A strict backup protects the file that is about to be replaced, not the
402 // ROM's original load path. A new target has no previous bytes to protect.
403 if (target_exists) {
404 auto now = std::chrono::system_clock::now();
405 auto now_c = std::chrono::system_clock::to_time_t(now);
406 std::string backup_filename =
407 absl::StrCat(filename, "_backup_", MakeSafeTimestamp(now_c));
408 RETURN_IF_ERROR(CreateRequiredBackup(target_path, backup_filename));
409 }
410 } else if (settings.backup) {
411 try {
412 const std::filesystem::path target_path(filename);
413 // Best-effort backups protect the file about to be replaced. A new
414 // destination has no previous bytes to preserve.
415 if (std::filesystem::exists(target_path)) {
416 auto now = std::chrono::system_clock::now();
417 auto now_c = std::chrono::system_clock::to_time_t(now);
418 std::string backup_filename =
419 absl::StrCat(filename, "_backup_", MakeSafeTimestamp(now_c));
420 std::filesystem::copy(
421 target_path, backup_filename,
422 std::filesystem::copy_options::overwrite_existing);
423 }
424 } catch (const std::filesystem::filesystem_error& e) {
425 LOG_WARN("Rom", "Could not create backup: %s", e.what());
426 }
427 }
428
429 // Save stability: write to a temp file in the same directory and rename into
430 // place. If we crash mid-write, the original ROM stays intact.
431 const std::filesystem::path target_path(filename);
432 std::filesystem::path temp_path = target_path;
433 temp_path += ".tmp";
434
435 std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
436 if (!file) {
437 return absl::InternalError(absl::StrCat(
438 "Could not open temp ROM file for writing: ", temp_path.string()));
439 }
440
441 file.write(reinterpret_cast<const char*>(rom_data_.data()), rom_data_.size());
442 file.flush();
443 if (!file) {
444 file.close();
445 std::error_code rm_ec;
446 std::filesystem::remove(temp_path, rm_ec);
447 return absl::InternalError(
448 absl::StrCat("Error while writing ROM file: ", temp_path.string()));
449 }
450
451 file.close();
452
453#if !defined(__EMSCRIPTEN__)
454 // Best-effort fsync so temp file contents are durable before rename.
455 BestEffortFsyncFile(temp_path);
456#endif
457
458 std::error_code rename_ec;
459 std::filesystem::rename(temp_path, target_path, rename_ec);
460#if defined(_WIN32)
461 // Windows may reject std::filesystem::rename when the destination exists.
462 // Replace it without deleting the original first, so a failed replacement
463 // does not leave the target missing.
464 if (rename_ec) {
465 if (MoveFileExW(temp_path.wstring().c_str(), target_path.wstring().c_str(),
467 rename_ec.clear();
468 } else {
469 rename_ec = std::error_code(static_cast<int>(GetLastError()),
470 std::system_category());
471 }
472 }
473#endif
474 if (rename_ec) {
475 std::error_code rm_ec;
476 std::filesystem::remove(temp_path, rm_ec);
477 return absl::InternalError(absl::StrCat(
478 "Failed to move temp ROM into place: ", rename_ec.message()));
479 }
480
481#if !defined(__EMSCRIPTEN__)
482 // Best-effort fsync the parent dir so the rename is durable.
483 BestEffortFsyncParentDir(target_path);
484#endif
485
486 dirty_ = false;
487 return absl::OkStatus();
488}
489
491 if (fence == nullptr) {
492 return;
493 }
494 write_fence_stack_.push_back(fence);
495}
496
498 if (fence == nullptr) {
499 return;
500 }
501 if (!write_fence_stack_.empty() && write_fence_stack_.back() == fence) {
502 write_fence_stack_.pop_back();
503 return;
504 }
505
506 // Defensive: avoid leaving a stale fence active if call sites mismatch.
507 for (auto it = write_fence_stack_.rbegin(); it != write_fence_stack_.rend();
508 ++it) {
509 if (*it == fence) {
510 write_fence_stack_.erase(std::next(it).base());
511 LOG_WARN("Rom", "Popped non-top write fence (mismatched scope)");
512 return;
513 }
514 }
515 LOG_WARN("Rom", "PopWriteFence called for unknown fence");
516}
517
518absl::StatusOr<uint8_t> Rom::ReadByte(int offset) const {
519 if (offset < 0 || offset >= static_cast<int>(rom_data_.size())) {
520 return absl::OutOfRangeError(absl::StrFormat(
521 "Offset %d out of range (size: %d)", offset, rom_data_.size()));
522 }
523 return rom_data_[offset];
524}
525
526absl::StatusOr<uint16_t> Rom::ReadWord(int offset) const {
527 if (offset < 0 || offset + 1 >= static_cast<int>(rom_data_.size())) {
528 return absl::OutOfRangeError("Offset out of range");
529 }
530 return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8));
531}
532
533absl::StatusOr<uint32_t> Rom::ReadLong(int offset) const {
534 if (offset < 0 || offset + 2 >= static_cast<int>(rom_data_.size())) {
535 return absl::OutOfRangeError("Offset out of range");
536 }
537 return (uint32_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8) |
538 (rom_data_[offset + 2] << 16));
539}
540
541absl::StatusOr<std::vector<uint8_t>> Rom::ReadByteVector(
542 uint32_t offset, uint32_t length) const {
543 if (offset + length > static_cast<uint32_t>(rom_data_.size())) {
544 return absl::OutOfRangeError("Offset and length out of range");
545 }
546 std::vector<uint8_t> result;
547 result.reserve(length);
548 for (uint32_t i = offset; i < offset + length; i++) {
549 result.push_back(rom_data_[i]);
550 }
551 return result;
552}
553
554absl::StatusOr<gfx::Tile16> Rom::ReadTile16(uint32_t tile16_id,
555 uint32_t tile16_ptr) {
556 // Skip 8 bytes per tile.
557 auto tpos = tile16_ptr + (tile16_id * 0x08);
558 gfx::Tile16 tile16 = {};
561 tpos += 2;
564 tpos += 2;
567 tpos += 2;
570 return tile16;
571}
572
573absl::Status Rom::WriteTile16(int tile16_id, uint32_t tile16_ptr,
574 const gfx::Tile16& tile) {
575 auto tpos = tile16_ptr + (tile16_id * 0x08);
577 tpos += 2;
579 tpos += 2;
581 tpos += 2;
583 return absl::OkStatus();
584}
585
586absl::Status Rom::WriteByte(int addr, uint8_t value) {
587 if (addr < 0 || addr >= static_cast<int>(rom_data_.size())) {
588 return absl::OutOfRangeError("Address out of range");
589 }
590 for (auto* fence : write_fence_stack_) {
591 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr), 1, "WriteByte"));
592 }
593 const uint8_t old_val = rom_data_[addr];
594 rom_data_[addr] = value;
595 dirty_ = true;
596#ifdef __EMSCRIPTEN__
597 MaybeBroadcastChange(addr, {old_val}, {value});
598#endif
599 for (auto* fence : write_fence_stack_) {
600 fence->RecordWrite(static_cast<uint32_t>(addr), 1);
601 }
602 return absl::OkStatus();
603}
604
605absl::Status Rom::WriteWord(int addr, uint16_t value) {
606 if (addr < 0 || addr + 1 >= static_cast<int>(rom_data_.size())) {
607 return absl::OutOfRangeError("Address out of range");
608 }
609 for (auto* fence : write_fence_stack_) {
610 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr), 2, "WriteWord"));
611 }
612 const uint8_t old0 = rom_data_[addr];
613 const uint8_t old1 = rom_data_[addr + 1];
614 rom_data_[addr] = (uint8_t)(value & 0xFF);
615 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
616 dirty_ = true;
617#ifdef __EMSCRIPTEN__
619 {static_cast<uint8_t>(value & 0xFF),
620 static_cast<uint8_t>((value >> 8) & 0xFF)});
621#endif
622 for (auto* fence : write_fence_stack_) {
623 fence->RecordWrite(static_cast<uint32_t>(addr), 2);
624 }
625 return absl::OkStatus();
626}
627
628absl::Status Rom::WriteShort(int addr, uint16_t value) {
629 return WriteWord(addr, value);
630}
631
632absl::Status Rom::WriteLong(uint32_t addr, uint32_t value) {
633 if (addr + 2 >= static_cast<uint32_t>(rom_data_.size())) {
634 return absl::OutOfRangeError("Address out of range");
635 }
636 for (auto* fence : write_fence_stack_) {
637 RETURN_IF_ERROR(fence->Check(addr, 3, "WriteLong"));
638 }
639 const uint8_t old0 = rom_data_[addr];
640 const uint8_t old1 = rom_data_[addr + 1];
641 const uint8_t old2 = rom_data_[addr + 2];
642 rom_data_[addr] = (uint8_t)(value & 0xFF);
643 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
644 rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF);
645 dirty_ = true;
646#ifdef __EMSCRIPTEN__
648 {static_cast<uint8_t>(value & 0xFF),
649 static_cast<uint8_t>((value >> 8) & 0xFF),
650 static_cast<uint8_t>((value >> 16) & 0xFF)});
651#endif
652 for (auto* fence : write_fence_stack_) {
653 fence->RecordWrite(addr, 3);
654 }
655 return absl::OkStatus();
656}
657
658absl::Status Rom::WriteVector(int addr, std::vector<uint8_t> data) {
659 if (addr < 0) {
660 return absl::OutOfRangeError("Address out of range");
661 }
662 if (addr + static_cast<int>(data.size()) >
663 static_cast<int>(rom_data_.size())) {
664 return absl::OutOfRangeError("Address out of range");
665 }
666 for (auto* fence : write_fence_stack_) {
667 RETURN_IF_ERROR(fence->Check(static_cast<uint32_t>(addr),
668 static_cast<uint32_t>(data.size()),
669 "WriteVector"));
670 }
671 std::vector<uint8_t> old_data;
672 old_data.reserve(data.size());
673 for (int i = 0; i < static_cast<int>(data.size()); i++) {
674 old_data.push_back(rom_data_[addr + i]);
675 rom_data_[addr + i] = data[i];
676 }
677 dirty_ = true;
678#ifdef __EMSCRIPTEN__
679 MaybeBroadcastChange(addr, old_data, data);
680#endif
681 for (auto* fence : write_fence_stack_) {
682 fence->RecordWrite(static_cast<uint32_t>(addr),
683 static_cast<uint32_t>(data.size()));
684 }
685 return absl::OkStatus();
686}
687
688absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor& color) {
689 uint16_t bgr = ((color.snes() >> 10) & 0x1F) | ((color.snes() & 0x1F) << 10) |
690 (color.snes() & 0x7C00);
691 return WriteWord(address, bgr);
692}
693
694absl::Status Rom::WriteHelper(const WriteAction& action) {
695 if (std::holds_alternative<uint8_t>(action.value)) {
696 return WriteByte(action.address, std::get<uint8_t>(action.value));
697 } else if (std::holds_alternative<uint16_t>(action.value) ||
698 std::holds_alternative<short>(action.value)) {
699 return WriteShort(action.address, std::get<uint16_t>(action.value));
700 } else if (std::holds_alternative<std::vector<uint8_t>>(action.value)) {
701 return WriteVector(action.address,
702 std::get<std::vector<uint8_t>>(action.value));
703 } else if (std::holds_alternative<gfx::SnesColor>(action.value)) {
704 return WriteColor(action.address, std::get<gfx::SnesColor>(action.value));
705 }
706 return absl::InvalidArgumentError("Invalid write argument type");
707}
708
709} // namespace yaze
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:541
void PushWriteFence(rom::WriteFence *fence)
Definition rom.cc:490
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:227
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
Definition rom.cc:554
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:688
auto filename() const
Definition rom.h:157
void PopWriteFence(rom::WriteFence *fence)
Definition rom.cc:497
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:586
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:518
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
Definition rom.cc:573
const auto & vector() const
Definition rom.h:155
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:658
std::string title_
Definition rom.h:180
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:371
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:526
std::vector< uint8_t > rom_data_
Definition rom.h:189
auto data() const
Definition rom.h:151
std::vector< rom::WriteFence * > write_fence_stack_
Definition rom.h:198
bool dirty_
Definition rom.h:195
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:335
std::string filename_
Definition rom.h:183
unsigned long size_
Definition rom.h:177
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:628
project::ResourceLabelManager resource_label_manager_
Definition rom.h:192
std::string short_name_
Definition rom.h:186
absl::Status WriteWord(int addr, uint16_t value)
Definition rom.cc:605
virtual absl::Status WriteHelper(const WriteAction &action)
Definition rom.cc:694
absl::Status WriteLong(uint32_t addr, uint32_t value)
Definition rom.cc:632
absl::StatusOr< uint32_t > ReadLong(int offset) const
Definition rom.cc:533
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:112
bool LoadLabels(const std::string &filename)
Definition project.cc:2251