16#include <system_error>
19#include "absl/cleanup/cleanup.h"
20#include "absl/status/status.h"
21#include "absl/status/statusor.h"
22#include "absl/strings/str_cat.h"
23#include "absl/strings/str_format.h"
24#include "absl/strings/string_view.h"
36#include <emscripten.h>
63 if (offset + 0x20 > size) {
66 const uint8_t map_mode = data[offset + 0x15];
67 const uint16_t complement = data[offset + 0x1C] | (data[offset + 0x1D] << 8);
68 const uint16_t checksum = data[offset + 0x1E] | (data[offset + 0x1F] << 8);
69 return (map_mode & 0xE0) == 0x20 && (complement ^ checksum) == 0xFFFF;
78 std::vector<uint8_t>& header,
87 LOG_INFO(
"Rom",
"Stripped copier header (%s); ROM size 0x%zX",
92 std::string timestamp = std::ctime(&now_c);
93 timestamp.erase(std::remove(timestamp.begin(), timestamp.end(),
'\n'),
95 std::replace(timestamp.begin(), timestamp.end(),
' ',
'_');
99 for (
char& ch : timestamp) {
120 const std::filesystem::path& requested_path) {
121 std::filesystem::path candidate = requested_path;
122 for (
int suffix = 1; suffix <= 1000; ++suffix) {
123 std::error_code exists_ec;
124 const bool exists = std::filesystem::exists(candidate, exists_ec);
126 return absl::InternalError(absl::StrCat(
127 "Could not inspect required ROM backup path: ", candidate.string(),
128 ": ", exists_ec.message()));
133 candidate = requested_path.string() +
"_" + std::to_string(suffix);
136 return absl::ResourceExhaustedError(
137 "Could not allocate a unique required ROM backup path");
140#if !defined(__EMSCRIPTEN__)
141void BestEffortFsyncFile(
const std::filesystem::path& path);
142void BestEffortFsyncParentDir(
const std::filesystem::path& file_path);
146 const std::filesystem::path& source_path,
147 const std::filesystem::path& requested_backup_path) {
149 if (!backup_path_or.ok()) {
150 return backup_path_or.status();
153 const std::filesystem::path backup_path = *backup_path_or;
154 std::filesystem::path temp_path = backup_path;
157 std::error_code copy_ec;
158 const bool copied = std::filesystem::copy_file(
159 source_path, temp_path, std::filesystem::copy_options::none, copy_ec);
160 if (!copied || copy_ec) {
161 std::error_code cleanup_ec;
162 std::filesystem::remove(temp_path, cleanup_ec);
163 return absl::FailedPreconditionError(absl::StrCat(
164 "Could not create required ROM backup: ", source_path.string(),
" -> ",
165 backup_path.string(),
": ",
166 copy_ec ? copy_ec.message() :
"copy did not complete"));
169#if !defined(__EMSCRIPTEN__)
173 std::error_code rename_ec;
174 std::filesystem::rename(temp_path, backup_path, rename_ec);
176 std::error_code cleanup_ec;
177 std::filesystem::remove(temp_path, cleanup_ec);
178 return absl::FailedPreconditionError(absl::StrCat(
179 "Could not finalize required ROM backup: ", backup_path.string(),
": ",
180 rename_ec.message()));
183#if !defined(__EMSCRIPTEN__)
187 return absl::OkStatus();
197 const std::filesystem::path& target_path,
198 const std::vector<uint8_t>& bytes) {
200 return absl::InternalError(
201 "Could not create temp ROM file: staging failure injected for testing");
203 static std::atomic<uint64_t> sequence{0};
204 for (
int attempt = 0; attempt < 100; ++attempt) {
205 const auto tick =
static_cast<uint64_t
>(
206 std::chrono::steady_clock::now().time_since_epoch().count());
207 const auto id = sequence.fetch_add(1, std::memory_order_relaxed);
208 const auto temp_path =
209 target_path.parent_path() /
210 absl::StrFormat(
".yaze-rom-%016x-%016x.tmp", tick,
id);
213 CreateFileW(temp_path.wstring().c_str(), GENERIC_WRITE, 0,
nullptr,
214 CREATE_NEW, FILE_ATTRIBUTE_NORMAL,
nullptr);
215 if (file == INVALID_HANDLE_VALUE) {
216 const DWORD error = GetLastError();
217 if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) {
220 return absl::InternalError(absl::StrCat(
221 "Could not create temp ROM file: ", temp_path.string(),
": ",
222 std::error_code(
static_cast<int>(error), std::system_category())
226 int file = open(temp_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0666);
228 const int error = errno;
229 if (error == EEXIST) {
232 return absl::InternalError(absl::StrCat(
233 "Could not create temp ROM file: ", temp_path.string(),
": ",
234 std::error_code(error, std::generic_category()).message()));
239 absl::Cleanup cleanup = [&] {
241 if (file != INVALID_HANDLE_VALUE) {
242 (void)CloseHandle(file);
250 std::filesystem::remove(temp_path, ec);
254 while (offset < bytes.size()) {
255 const size_t chunk = std::min<size_t>(bytes.size() - offset, 1024 * 1024);
258 const bool ok = WriteFile(file, bytes.data() + offset,
259 static_cast<DWORD
>(chunk), &written,
nullptr);
261 ok ? ERROR_WRITE_FAULT :
static_cast<int>(GetLastError());
262 if (!ok || written == 0) {
263 return absl::InternalError(absl::StrCat(
264 "Error while writing ROM file: ", temp_path.string(),
": ",
265 std::error_code(error, std::system_category()).message()));
268 const ssize_t written = write(file, bytes.data() + offset, chunk);
269 if (written < 0 && errno == EINTR) {
273 const int error = written < 0 ? errno : EIO;
274 return absl::InternalError(absl::StrCat(
275 "Error while writing ROM file: ", temp_path.string(),
": ",
276 std::error_code(error, std::generic_category()).message()));
279 offset +=
static_cast<size_t>(written);
283 if (!FlushFileBuffers(file)) {
284 const int error =
static_cast<int>(GetLastError());
285 return absl::InternalError(absl::StrCat(
286 "Could not flush temp ROM file: ", temp_path.string(),
": ",
287 std::error_code(error, std::system_category()).message()));
289 const bool closed = CloseHandle(file);
290 const int close_error = closed ? 0 :
static_cast<int>(GetLastError());
291 file = INVALID_HANDLE_VALUE;
292 const auto close_category = &std::system_category();
294#if !defined(__EMSCRIPTEN__)
297 flush_result = fsync(file);
298 }
while (flush_result != 0 && errno == EINTR);
299 if (flush_result != 0) {
300 const int error = errno;
301 return absl::InternalError(absl::StrCat(
302 "Could not flush temp ROM file: ", temp_path.string(),
": ",
303 std::error_code(error, std::generic_category()).message()));
306 const int close_result = close(file);
307 const int close_error = close_result == 0 ? 0 : errno;
309 const auto close_category = &std::generic_category();
311 if (close_error != 0) {
312 return absl::InternalError(absl::StrCat(
313 "Could not close temp ROM file: ", temp_path.string(),
": ",
314 std::error_code(close_error, *close_category).message()));
316 std::move(cleanup).Cancel();
319 return absl::ResourceExhaustedError(
320 "Could not allocate a unique temp ROM file");
324inline void MaybeBroadcastChange(uint32_t offset,
325 const std::vector<uint8_t>& old_bytes,
326 const std::vector<uint8_t>& new_bytes) {
327 if (new_bytes.empty())
329 auto& collab = app::platform::GetWasmCollaborationInstance();
330 if (!collab.IsConnected() || collab.IsApplyingRemoteChange()) {
333 (void)collab.BroadcastChange(offset, old_bytes, new_bytes);
337#if !defined(__EMSCRIPTEN__)
342 CreateFileW(path.wstring().c_str(), GENERIC_WRITE,
343 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
344 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
nullptr);
345 if (handle == INVALID_HANDLE_VALUE) {
348 (void)FlushFileBuffers(handle);
349 (void)CloseHandle(handle);
351 int fd = open(path.c_str(), O_RDONLY);
365 std::filesystem::path dir_path = file_path.parent_path();
366 if (dir_path.empty()) {
369 int fd = open(dir_path.c_str(), O_RDONLY);
385 const size_t remainder = size % kCopierHeaderAlignment;
386 if (remainder == 0) {
389 const bool header_in_place = HasInternalHeader(data, size, 0);
390 const bool header_after_skip =
395 return header_in_place && !header_after_skip
400 return header_after_skip && !header_in_place
410 return "size_alignment";
412 return "internal_header_offset";
418 g_fail_rom_staging_for_testing.store(
fail, std::memory_order_relaxed);
426 result.write_supported =
false;
427 result.error =
"Loaded Lyra descriptor was removed or damaged";
434 return absl::PermissionDeniedError(
435 "ROM write refused by read_only project policy");
439 return absl::OkStatus();
441 return absl::FailedPreconditionError(
443 ? (layout.a0_terrain_write_supported
444 ?
"Lyra A0 terrain requires dedicated Save As; generic save "
446 :
"Lyra overworld ROM layout is inspection-only; save refused")
447 : absl::StrCat(
"Invalid Lyra overworld ROM layout: ", layout.error,
458 return absl::OkStatus();
459 std::vector<std::pair<uint32_t, uint32_t>> ranges;
461 ranges.emplace_back(base, base + 0x67E0);
466 ranges.emplace_back(0x1759D, 0x175D2);
467 ranges.emplace_back(0x17691, 0x177CB);
468 ranges.emplace_back(0x177E3, 0x17818);
469 ranges.emplace_back(0x78000, 0x80000);
470 ranges.emplace_back(0x1E8000, 0x1F0000);
471 ranges.emplace_back(0x132000, 0x132400);
472 ranges.emplace_back(
contract.stream_pc_range[0],
contract.stream_pc_range[1]);
473 for (
uint32_t table : {0x1794Du, 0x17B2Du}) {
474 ranges.emplace_back(table, table + 160 * 3);
475 for (
int map = 0; map < 160; ++map) {
481 if (decoded.data.size() != 256 ||
482 pc + decoded.compressed_size >
rom_data_.size())
483 return absl::FailedPreconditionError(
484 "Private Lyra native stream is invalid");
485 ranges.emplace_back(pc, pc + decoded.compressed_size);
488 std::sort(ranges.begin(), ranges.end());
489 std::vector<std::pair<uint32_t, uint32_t>>
merged;
490 for (
auto range : ranges) {
491 if (!
merged.empty() && range.first <=
merged.back().second)
492 merged.back().second = std::max(
merged.back().second, range.second);
496 auto fence = std::make_shared<rom::WriteFence>();
497 std::vector<std::pair<uint32_t, std::vector<uint8_t>>>
preimages;
501 return absl::FailedPreconditionError(
"Private Lyra range outside ROM");
511 fence->Allow(cursor,
rom_data_.size(),
"outside private terrain"));
515 return absl::OkStatus();
523 return absl::FailedPreconditionError(
"Private Lyra manifest is not loaded");
526 return absl::FailedPreconditionError(
527 "Private Lyra reservation contract mismatch");
532 return absl::FailedPreconditionError(
533 "Private Lyra reservation source ROM hash mismatch");
534 return absl::OkStatus();
538 const std::vector<uint8_t>& image)
const {
540 return absl::OkStatus();
541 if (
image.size() != 0x400000) {
542 return absl::FailedPreconditionError(
543 "Private Lyra exact-save ROM size changed");
546 if (pc >
image.size() || bytes.size() >
image.size() - pc ||
547 !std::equal(bytes.begin(), bytes.end(),
image.begin() + pc))
548 return absl::FailedPreconditionError(
549 "Private Lyra reservation protected terrain changed; native save "
552 return absl::OkStatus();
562 return absl::OkStatus();
565 for (
size_t i = 0; i <
size; ++i) {
570 return absl::OkStatus();
574 : private_lyra_attached_(other.private_lyra_attached_),
575 private_lyra_preimages_(other.private_lyra_preimages_),
576 private_lyra_fence_(other.private_lyra_fence_),
578 title_(other.title_),
579 filename_(other.filename_),
580 short_name_(other.short_name_),
581 rom_data_(other.rom_data_),
582 lyra_terrain_baseline_(other.lyra_terrain_baseline_),
583 copier_header_(other.copier_header_),
584 copier_header_reason_(other.copier_header_reason_),
585 lyra_attributed_at_load_(other.lyra_attributed_at_load_),
586 resource_label_manager_(other.resource_label_manager_),
587 dirty_(other.dirty_),
588 object_tile_revision_(other.object_tile_revision_) {
594 if (
this == &other) {
635 return absl::InvalidArgumentError(
636 "Could not load ROM: parameter `filename` is empty.");
644 return absl::NotFoundError(
"Could not open ROM: " +
filename);
647 return absl::InvalidArgumentError(
648 "Attached ROM file must be between 32KB and 16MB");
650 std::vector<uint8_t> candidate(
static_cast<size_t>(length));
652 if (!
candidate_file.read(
reinterpret_cast<char*
>(candidate.data()),
654 return absl::DataLossError(
655 "Could not read complete attached ROM snapshot");
666 absl::StrFormat(
"%s.labels",
filename));
668 return absl::OkStatus();
673 std::ifstream test_file(
filename_, std::ios::binary);
674 if (!test_file.is_open()) {
675 return absl::NotFoundError(absl::StrCat(
676 "ROM file does not exist or cannot be opened: ",
filename_));
678 test_file.seekg(0, std::ios::end);
679 size_ = test_file.tellg();
683 return absl::InvalidArgumentError(absl::StrFormat(
684 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
687 if (!std::filesystem::exists(
filename)) {
688 return absl::NotFoundError(
689 absl::StrCat(
"ROM file does not exist: ",
filename));
695 std::ifstream file(
filename_, std::ios::binary);
696 if (!file.is_open()) {
697 return absl::NotFoundError(
698 absl::StrCat(
"Could not open ROM file: ",
filename_));
701#ifndef __EMSCRIPTEN__
705 return absl::InvalidArgumentError(absl::StrFormat(
706 "ROM file too small (%zu bytes), minimum is 32KB",
size_));
709 file.seekg(0, std::ios::end);
710 size_ = file.tellg();
718 return absl::InvalidArgumentError(absl::StrFormat(
719 "ROM file too large (%zu bytes), maximum is 16MB",
size_));
724 file.seekg(0, std::ios::beg);
726 }
catch (
const std::bad_alloc&
e) {
727 return absl::ResourceExhaustedError(absl::StrFormat(
728 "Failed to allocate memory for ROM (%zu bytes)",
size_));
760 char buffer[22] = {0};
761 for (
int i = 0; i < 21; ++i) {
763 buffer[i] = (c >= 32 && c <= 126) ? c :
' ';
765 title_ = std::string(buffer);
778 return absl::OkStatus();
784 return absl::InvalidArgumentError(
785 "Could not load ROM: parameter `data` is empty.");
788 auto candidate =
data;
789 std::vector<uint8_t> header;
792 MaybeStripCopierHeader(candidate, header, reason);
813 char buffer[22] = {0};
814 for (
int i = 0; i < 21; ++i) {
816 buffer[i] = (c >= 32 && c <= 126) ? c :
' ';
818 title_ = std::string(buffer);
830 return absl::OkStatus();
837 layout.a0_terrain_write_supported) {
843 const std::string& filename) {
845 return absl::FailedPreconditionError(
846 "Private Lyra reservation forbids alternate A0 save route");
848 return absl::PermissionDeniedError(
849 "ROM write refused by read_only project policy");
852 return absl::UnimplementedError(
853 "Lyra terrain Save As requires native storage");
857 return absl::FailedPreconditionError(
858 "Lyra terrain Save As requires the unchanged loaded ROM snapshot");
861 const auto target = std::filesystem::weakly_canonical(
filename,
path_ec);
863 return absl::InvalidArgumentError(
"Cannot resolve Lyra Save As target");
868 return absl::InvalidArgumentError(
"Cannot resolve Lyra source ROM path");
871 std::filesystem::equivalent(source, target,
path_ec);
873 return absl::FailedPreconditionError(
874 "Lyra terrain requires Save As to a different backing file");
879 Rom candidate(*
this);
889 return absl::OkStatus();
902 return absl::InternalError(
"ROM data is empty.");
913 auto now = std::chrono::system_clock::now();
914 auto now_c = std::chrono::system_clock::to_time_t(
now);
921 const std::filesystem::path target_path(
filename);
925 return absl::FailedPreconditionError(absl::StrCat(
926 "Could not inspect required ROM backup target: ",
filename,
": ",
933 auto now = std::chrono::system_clock::now();
934 auto now_c = std::chrono::system_clock::to_time_t(
now);
936 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
939 }
else if (settings.
backup) {
941 const std::filesystem::path target_path(
filename);
944 if (std::filesystem::exists(target_path)) {
945 auto now = std::chrono::system_clock::now();
946 auto now_c = std::chrono::system_clock::to_time_t(
now);
948 absl::StrCat(
filename,
"_backup_", MakeSafeTimestamp(
now_c));
949 std::filesystem::copy(
951 std::filesystem::copy_options::overwrite_existing);
953 }
catch (
const std::filesystem::filesystem_error&
e) {
954 LOG_WARN(
"Rom",
"Could not create backup: %s",
e.what());
972 const std::filesystem::path target_path(
filename);
974 WriteExclusiveRomTemp(target_path,
file_bytes));
977 std::filesystem::rename(temp_path, target_path,
rename_ec);
983 if (
MoveFileExW(temp_path.wstring().c_str(), target_path.wstring().c_str(),
988 std::system_category());
993 std::error_code
rm_ec;
994 std::filesystem::remove(temp_path,
rm_ec);
995 return absl::InternalError(absl::StrCat(
996 "Failed to move temp ROM into place: ",
rename_ec.message()));
999#if !defined(__EMSCRIPTEN__)
1001 BestEffortFsyncParentDir(target_path);
1005 return absl::OkStatus();
1009 if (
fence ==
nullptr) {
1016 if (
fence ==
nullptr) {
1029 LOG_WARN(
"Rom",
"Popped non-top write fence (mismatched scope)");
1033 LOG_WARN(
"Rom",
"PopWriteFence called for unknown fence");
1038 return absl::OutOfRangeError(absl::StrFormat(
1039 "Offset %d out of range (size: %d)", offset,
rom_data_.size()));
1046 return absl::OutOfRangeError(
"Offset out of range");
1053 return absl::OutOfRangeError(
"Offset out of range");
1060 uint32_t offset, uint32_t length)
const {
1062 return absl::OutOfRangeError(
"Offset and length out of range");
1064 std::vector<uint8_t> result;
1065 result.reserve(length);
1066 for (
uint32_t i = offset; i < offset + length; i++) {
1073 uint32_t tile16_ptr) {
1101 return absl::OkStatus();
1107 return absl::OutOfRangeError(
"Address out of range");
1116#ifdef __EMSCRIPTEN__
1122 return absl::OkStatus();
1128 return absl::OutOfRangeError(
"Address out of range");
1134 static_cast<uint8_t>(value >> 8),
1135 static_cast<uint8_t>(value >> 16)};
1142#ifdef __EMSCRIPTEN__
1144 {
static_cast<uint8_t>(value & 0xFF),
1145 static_cast<uint8_t>((value >> 8) & 0xFF)});
1150 return absl::OkStatus();
1160 return absl::OutOfRangeError(
"Address out of range");
1166 static_cast<uint8_t>(value >> 8),
1167 static_cast<uint8_t>(value >> 16)};
1176#ifdef __EMSCRIPTEN__
1178 {
static_cast<uint8_t>(value & 0xFF),
1179 static_cast<uint8_t>((value >> 8) & 0xFF),
1180 static_cast<uint8_t>((value >> 16) & 0xFF)});
1183 fence->RecordWrite(addr, 3);
1185 return absl::OkStatus();
1191 return absl::OutOfRangeError(
"Address out of range");
1193 if (addr +
static_cast<int>(
data.size()) >
1195 return absl::OutOfRangeError(
"Address out of range");
1203 std::vector<uint8_t> old_data;
1204 old_data.reserve(
data.size());
1206 old_data.push_back(
rom_data_[addr + i]);
1210#ifdef __EMSCRIPTEN__
1217 return absl::OkStatus();
1222 (color.
snes() & 0x7C00);
1227 if (std::holds_alternative<uint8_t>(action.
value)) {
1229 }
else if (std::holds_alternative<uint16_t>(action.
value) ||
1230 std::holds_alternative<short>(action.
value)) {
1232 }
else if (std::holds_alternative<std::vector<uint8_t>>(action.
value)) {
1234 std::get<std::vector<uint8_t>>(action.
value));
1235 }
else if (std::holds_alternative<gfx::SnesColor>(action.
value)) {
1238 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())
std::vector< uint8_t > rom_data_
absl::Status CheckLayoutWritable() const
absl::Status AttachLyraPrivateReservation(const core::HackManifest &manifest)
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)
rom::LyraLayoutInspection InspectLyraLayout() const
absl::StatusOr< uint8_t > ReadByte(int offset) const
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
std::vector< std::pair< uint32_t, std::vector< uint8_t > > > private_lyra_preimages_
const auto & vector() const
std::shared_ptr< rom::WriteFence > private_lyra_fence_
std::vector< uint8_t > copier_header_
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::Status CapturePrivateLyraReservation()
bool private_lyra_attached_
absl::Status SaveToFile(const SaveSettings &settings)
absl::StatusOr< uint16_t > ReadWord(int offset) const
static void SetStagingFailureForTesting(bool fail)
CopierHeaderReason copier_header_reason_
std::vector< rom::WriteFence * > write_fence_stack_
absl::Status SaveToFileUnchecked(const SaveSettings &settings)
Rom & operator=(const Rom &other)
void CaptureLyraTerrainBaseline()
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
void AdvanceObjectTileRevision()
std::vector< uint8_t > lyra_terrain_baseline_
absl::Status ValidateLyraPrivateReservation() const
absl::Status WriteShort(int addr, uint16_t value)
absl::Status ValidatePrivateLyraImage(const std::vector< uint8_t > &image) const
project::ResourceLabelManager resource_label_manager_
absl::Status SaveLyraA0TerrainAs(const std::array< uint16_t, 256 > &tiles, const std::string &filename)
absl::Status CheckPrivateLyraWrite(uint32_t pc, const uint8_t *bytes, size_t size) const
size_t read_only_scope_depth_
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
bool lyra_attributed_at_load_
Loads and queries the hack manifest JSON for yaze-ASM integration.
const std::optional< LyraPrivateReservation > & lyra_private_reservation() const
bool loaded() const
Check if the manifest has been loaded.
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)
std::atomic< bool > g_fail_rom_staging_for_testing
absl::Status CreateRequiredBackup(const std::filesystem::path &source_path, const std::filesystem::path &requested_backup_path)
void BestEffortFsyncFile(const std::filesystem::path &path)
constexpr size_t kLoRomInternalHeader
bool HasInternalHeaderAt(const uint8_t *data, size_t size, size_t offset)
absl::StatusOr< std::filesystem::path > WriteExclusiveRomTemp(const std::filesystem::path &target_path, const std::vector< uint8_t > &bytes)
void BestEffortFsyncParentDir(const std::filesystem::path &file_path)
constexpr size_t kCopierHeaderAlignment
SNES ROM images are multiples of 32 KB; a copier header adds 0x200.
void MaybeStripCopierHeader(std::vector< uint8_t > &rom_data, std::vector< uint8_t > &header, CopierHeaderReason &reason)
constexpr size_t kHiRomInternalHeader
bool HasInternalHeader(const uint8_t *data, size_t size, size_t base)
absl::StatusOr< std::filesystem::path > GetAvailableBackupPath(const std::filesystem::path &requested_path)
std::string ComputeSourceArtifactSha256(std::string_view content)
LyraPrivateReservation PrivateLyraExactSaveReservation()
absl::StatusOr< ExactDecodeResult > DecompressExact(const uint8_t *data, size_t data_size, size_t offset, size_t max_output, bool big_endian_copy)
Decodes one LC-LZ2 stream with the semantics of the ALTTP bank-00 decompressor (Decompress at $00:E79...
uint16_t TileInfoToWord(TileInfo tile_info)
TileInfo WordToTileInfo(uint16_t word)
LyraLayoutInspection InspectLyraLayout(const uint8_t *image, size_t size)
absl::StatusOr< std::vector< uint8_t > > SerializeLyraA0Terrain(const std::vector< uint8_t > &baseline, const std::array< uint16_t, 256 > &tiles)
CopierHeaderReason DetectCopierHeader(const uint8_t *data, size_t size)
CopierHeaderReason
Why a copier header was recognized at the start of a ROM file.
constexpr size_t kCopierHeaderSize
Size of the SMC/SWC copier header that some ROM dumps start with.
uint32_t SnesToPc(uint32_t addr) noexcept
const char * CopierHeaderReasonName(CopierHeaderReason reason)
#define RETURN_IF_ERROR(expr)
bool load_resource_labels
bool LoadLabels(const std::string &filename)
LyraLayoutPresence presence