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);
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_) {
241 if (
this == &
other) {
273 return absl::InvalidArgumentError(
274 "Could not load ROM: parameter `filename` is empty.");
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_));
284 test_file.seekg(0, std::ios::end);
285 size_ = test_file.tellg();
289 return absl::InvalidArgumentError(absl::StrFormat(
290 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
293 if (!std::filesystem::exists(
filename)) {
294 return absl::NotFoundError(
295 absl::StrCat(
"ROM file does not exist: ",
filename));
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_));
307#ifndef __EMSCRIPTEN__
311 return absl::InvalidArgumentError(absl::StrFormat(
312 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
315 file.seekg(0, std::ios::end);
316 size_ = file.tellg();
324 return absl::InvalidArgumentError(absl::StrFormat(
325 "ROM file too large (%zu bytes), maximum is 16MB",
size_));
330 file.seekg(0, std::ios::beg);
332 }
catch (
const std::bad_alloc&
e) {
333 return absl::ResourceExhaustedError(absl::StrFormat(
334 "Failed to allocate memory for ROM (%zu bytes)",
size_));
359 char buffer[22] = {0};
360 for (
int i = 0; i < 21; ++i) {
362 buffer[i] = (
c >= 32 &&
c <= 126) ?
c :
' ';
364 title_ = std::string(buffer);
376 return absl::OkStatus();
382 return absl::InvalidArgumentError(
383 "Could not load ROM: parameter `data` is empty.");
397 char buffer[22] = {0};
398 for (
int i = 0; i < 21; ++i) {
400 buffer[i] = (
c >= 32 &&
c <= 126) ?
c :
' ';
402 title_ = std::string(buffer);
413 return absl::OkStatus();
418 return absl::InternalError(
"ROM data is empty.");
429 auto now = std::chrono::system_clock::now();
430 auto now_c = std::chrono::system_clock::to_time_t(
now);
437 const std::filesystem::path target_path(
filename);
441 return absl::FailedPreconditionError(absl::StrCat(
442 "Could not inspect required ROM backup target: ",
filename,
": ",
449 auto now = std::chrono::system_clock::now();
450 auto now_c = std::chrono::system_clock::to_time_t(
now);
452 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
455 }
else if (settings.
backup) {
457 const std::filesystem::path target_path(
filename);
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);
464 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
465 std::filesystem::copy(
467 std::filesystem::copy_options::overwrite_existing);
469 }
catch (
const std::filesystem::filesystem_error&
e) {
470 LOG_WARN(
"Rom",
"Could not create backup: %s",
e.what());
476 const std::filesystem::path target_path(
filename);
477 std::filesystem::path temp_path = target_path;
480 std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
482 return absl::InternalError(absl::StrCat(
483 "Could not open temp ROM file for writing: ", temp_path.string()));
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()));
498#if !defined(__EMSCRIPTEN__)
500 BestEffortFsyncFile(temp_path);
504 std::filesystem::rename(temp_path, target_path,
rename_ec);
510 if (
MoveFileExW(temp_path.wstring().c_str(), target_path.wstring().c_str(),
515 std::system_category());
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()));
526#if !defined(__EMSCRIPTEN__)
528 BestEffortFsyncParentDir(target_path);
532 return absl::OkStatus();
536 if (
fence ==
nullptr) {
543 if (
fence ==
nullptr) {
556 LOG_WARN(
"Rom",
"Popped non-top write fence (mismatched scope)");
560 LOG_WARN(
"Rom",
"PopWriteFence called for unknown fence");
565 return absl::OutOfRangeError(absl::StrFormat(
566 "Offset %d out of range (size: %d)", offset,
rom_data_.size()));
573 return absl::OutOfRangeError(
"Offset out of range");
580 return absl::OutOfRangeError(
"Offset out of range");
587 uint32_t offset, uint32_t length)
const {
589 return absl::OutOfRangeError(
"Offset and length out of range");
591 std::vector<uint8_t> result;
592 result.reserve(length);
593 for (
uint32_t i = offset; i < offset + length; i++) {
600 uint32_t tile16_ptr) {
628 return absl::OkStatus();
633 return absl::OutOfRangeError(
"Address out of range");
647 return absl::OkStatus();
652 return absl::OutOfRangeError(
"Address out of range");
664 {
static_cast<uint8_t>(value & 0xFF),
665 static_cast<uint8_t>((value >> 8) & 0xFF)});
670 return absl::OkStatus();
679 return absl::OutOfRangeError(
"Address out of range");
693 {
static_cast<uint8_t>(value & 0xFF),
694 static_cast<uint8_t>((value >> 8) & 0xFF),
695 static_cast<uint8_t>((value >> 16) & 0xFF)});
698 fence->RecordWrite(addr, 3);
700 return absl::OkStatus();
705 return absl::OutOfRangeError(
"Address out of range");
707 if (addr +
static_cast<int>(
data.size()) >
709 return absl::OutOfRangeError(
"Address out of range");
716 std::vector<uint8_t> old_data;
717 old_data.reserve(
data.size());
730 return absl::OkStatus();
735 (color.
snes() & 0x7C00);
740 if (std::holds_alternative<uint8_t>(action.
value)) {
742 }
else if (std::holds_alternative<uint16_t>(action.
value) ||
743 std::holds_alternative<short>(action.
value)) {
745 }
else if (std::holds_alternative<std::vector<uint8_t>>(action.
value)) {
747 std::get<std::vector<uint8_t>>(action.
value));
748 }
else if (std::holds_alternative<gfx::SnesColor>(action.
value)) {
751 return absl::InvalidArgumentError(
"Invalid write argument type");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
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_
Rom & operator=(const Rom &other)
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
void AdvanceObjectTileRevision()
absl::Status WriteShort(int addr, uint16_t value)
project::ResourceLabelManager resource_label_manager_
uint64_t object_tile_revision_
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)