14#include <system_error>
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"
30#include <emscripten.h>
34#if !defined(__EMSCRIPTEN__)
64 rom_data.erase(rom_data.begin(), rom_data.begin() +
kHeaderSize);
66 LOG_INFO(
"Rom",
"Stripped SMC header from ROM (new size: %lu)", size);
71 std::string timestamp = std::ctime(&now_c);
72 timestamp.erase(std::remove(timestamp.begin(), timestamp.end(),
'\n'),
74 std::replace(timestamp.begin(), timestamp.end(),
' ',
'_');
78 for (
char& ch : timestamp) {
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);
105 return absl::InternalError(absl::StrCat(
106 "Could not inspect required ROM backup path: ", candidate.string(),
107 ": ", exists_ec.message()));
112 candidate = requested_path.string() +
"_" + std::to_string(suffix);
115 return absl::ResourceExhaustedError(
116 "Could not allocate a unique required ROM backup path");
119#if !defined(__EMSCRIPTEN__)
120void BestEffortFsyncFile(
const std::filesystem::path& path);
121void BestEffortFsyncParentDir(
const std::filesystem::path& file_path);
125 const std::filesystem::path& source_path,
126 const std::filesystem::path& requested_backup_path) {
128 if (!backup_path_or.ok()) {
129 return backup_path_or.status();
132 const std::filesystem::path backup_path = *backup_path_or;
133 std::filesystem::path temp_path = backup_path;
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"));
148#if !defined(__EMSCRIPTEN__)
152 std::error_code rename_ec;
153 std::filesystem::rename(temp_path, backup_path, 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()));
162#if !defined(__EMSCRIPTEN__)
166 return absl::OkStatus();
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())
175 auto& collab = app::platform::GetWasmCollaborationInstance();
176 if (!collab.IsConnected() || collab.IsApplyingRemoteChange()) {
179 (void)collab.BroadcastChange(offset, old_bytes, new_bytes);
183#if !defined(__EMSCRIPTEN__)
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) {
194 (void)FlushFileBuffers(handle);
195 (void)CloseHandle(handle);
197 int fd = open(path.c_str(), O_RDONLY);
211 std::filesystem::path dir_path = file_path.parent_path();
212 if (dir_path.empty()) {
215 int fd = open(dir_path.c_str(), O_RDONLY);
230 return absl::InvalidArgumentError(
231 "Could not load ROM: parameter `filename` is empty.");
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_));
241 test_file.seekg(0, std::ios::end);
242 size_ = test_file.tellg();
246 return absl::InvalidArgumentError(absl::StrFormat(
247 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
250 if (!std::filesystem::exists(
filename)) {
251 return absl::NotFoundError(
252 absl::StrCat(
"ROM file does not exist: ",
filename));
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_));
264#ifndef __EMSCRIPTEN__
268 return absl::InvalidArgumentError(absl::StrFormat(
269 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
272 file.seekg(0, std::ios::end);
273 size_ = file.tellg();
281 return absl::InvalidArgumentError(absl::StrFormat(
282 "ROM file too large (%zu bytes), maximum is 16MB",
size_));
287 file.seekg(0, std::ios::beg);
289 }
catch (
const std::bad_alloc&
e) {
290 return absl::ResourceExhaustedError(absl::StrFormat(
291 "Failed to allocate memory for ROM (%zu bytes)",
size_));
316 char buffer[22] = {0};
317 for (
int i = 0; i < 21; ++i) {
319 buffer[i] = (
c >= 32 &&
c <= 126) ?
c :
' ';
321 title_ = std::string(buffer);
332 return absl::OkStatus();
338 return absl::InvalidArgumentError(
339 "Could not load ROM: parameter `data` is empty.");
353 char buffer[22] = {0};
354 for (
int i = 0; i < 21; ++i) {
356 buffer[i] = (
c >= 32 &&
c <= 126) ?
c :
' ';
358 title_ = std::string(buffer);
368 return absl::OkStatus();
373 return absl::InternalError(
"ROM data is empty.");
384 auto now = std::chrono::system_clock::now();
385 auto now_c = std::chrono::system_clock::to_time_t(
now);
392 const std::filesystem::path target_path(
filename);
396 return absl::FailedPreconditionError(absl::StrCat(
397 "Could not inspect required ROM backup target: ",
filename,
": ",
404 auto now = std::chrono::system_clock::now();
405 auto now_c = std::chrono::system_clock::to_time_t(
now);
407 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
410 }
else if (settings.
backup) {
412 const std::filesystem::path target_path(
filename);
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);
419 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
420 std::filesystem::copy(
422 std::filesystem::copy_options::overwrite_existing);
424 }
catch (
const std::filesystem::filesystem_error&
e) {
425 LOG_WARN(
"Rom",
"Could not create backup: %s",
e.what());
431 const std::filesystem::path target_path(
filename);
432 std::filesystem::path temp_path = target_path;
435 std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
437 return absl::InternalError(absl::StrCat(
438 "Could not open temp ROM file for writing: ", temp_path.string()));
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()));
453#if !defined(__EMSCRIPTEN__)
455 BestEffortFsyncFile(temp_path);
459 std::filesystem::rename(temp_path, target_path,
rename_ec);
465 if (
MoveFileExW(temp_path.wstring().c_str(), target_path.wstring().c_str(),
470 std::system_category());
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()));
481#if !defined(__EMSCRIPTEN__)
483 BestEffortFsyncParentDir(target_path);
487 return absl::OkStatus();
491 if (
fence ==
nullptr) {
498 if (
fence ==
nullptr) {
511 LOG_WARN(
"Rom",
"Popped non-top write fence (mismatched scope)");
515 LOG_WARN(
"Rom",
"PopWriteFence called for unknown fence");
520 return absl::OutOfRangeError(absl::StrFormat(
521 "Offset %d out of range (size: %d)", offset,
rom_data_.size()));
528 return absl::OutOfRangeError(
"Offset out of range");
535 return absl::OutOfRangeError(
"Offset out of range");
542 uint32_t offset, uint32_t length)
const {
544 return absl::OutOfRangeError(
"Offset and length out of range");
546 std::vector<uint8_t> result;
547 result.reserve(length);
548 for (
uint32_t i = offset; i < offset + length; i++) {
555 uint32_t tile16_ptr) {
583 return absl::OkStatus();
588 return absl::OutOfRangeError(
"Address out of range");
602 return absl::OkStatus();
607 return absl::OutOfRangeError(
"Address out of range");
619 {
static_cast<uint8_t>(value & 0xFF),
620 static_cast<uint8_t>((value >> 8) & 0xFF)});
625 return absl::OkStatus();
634 return absl::OutOfRangeError(
"Address out of range");
648 {
static_cast<uint8_t>(value & 0xFF),
649 static_cast<uint8_t>((value >> 8) & 0xFF),
650 static_cast<uint8_t>((value >> 16) & 0xFF)});
653 fence->RecordWrite(addr, 3);
655 return absl::OkStatus();
660 return absl::OutOfRangeError(
"Address out of range");
662 if (addr +
static_cast<int>(
data.size()) >
664 return absl::OutOfRangeError(
"Address out of range");
671 std::vector<uint8_t> old_data;
672 old_data.reserve(
data.size());
685 return absl::OkStatus();
690 (color.
snes() & 0x7C00);
695 if (std::holds_alternative<uint8_t>(action.
value)) {
697 }
else if (std::holds_alternative<uint16_t>(action.
value) ||
698 std::holds_alternative<short>(action.
value)) {
700 }
else if (std::holds_alternative<std::vector<uint8_t>>(action.
value)) {
702 std::get<std::vector<uint8_t>>(action.
value));
703 }
else if (std::holds_alternative<gfx::SnesColor>(action.
value)) {
706 return absl::InvalidArgumentError(
"Invalid write argument type");
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
void PushWriteFence(rom::WriteFence *fence)
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
void PopWriteFence(rom::WriteFence *fence)
absl::Status WriteByte(int addr, uint8_t value)
absl::StatusOr< uint8_t > ReadByte(int offset) const
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
const auto & vector() const
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::Status SaveToFile(const SaveSettings &settings)
absl::StatusOr< uint16_t > ReadWord(int offset) const
std::vector< uint8_t > rom_data_
std::vector< rom::WriteFence * > write_fence_stack_
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
absl::Status WriteShort(int addr, uint16_t value)
project::ResourceLabelManager resource_label_manager_
absl::Status WriteWord(int addr, uint16_t value)
virtual absl::Status WriteHelper(const WriteAction &action)
absl::Status WriteLong(uint32_t addr, uint32_t value)
absl::StatusOr< uint32_t > ReadLong(int offset) const
constexpr uint16_t snes() const
Get SNES 15-bit color.
Tile composition of four 8x8 tiles.
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::string MakeSafeTimestamp(std::time_t now_c)
absl::Status CreateRequiredBackup(const std::filesystem::path &source_path, const std::filesystem::path &requested_backup_path)
void BestEffortFsyncFile(const std::filesystem::path &path)
void BestEffortFsyncParentDir(const std::filesystem::path &file_path)
void MaybeStripSmcHeader(std::vector< uint8_t > &rom_data, unsigned long &size)
constexpr size_t kHeaderSize
Size of the optional SMC/SFC copier header that some ROM dumps include.
constexpr size_t kBaseRomSize
Standard SNES ROM size for The Legend of Zelda: A Link to the Past (1MB)
absl::StatusOr< std::filesystem::path > GetAvailableBackupPath(const std::filesystem::path &requested_path)
uint16_t TileInfoToWord(TileInfo tile_info)
TileInfo WordToTileInfo(uint16_t word)
#define RETURN_IF_ERROR(expr)
bool load_resource_labels
bool LoadLabels(const std::string &filename)