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 <vector>
15
16#include "absl/status/status.h"
17#include "absl/status/statusor.h"
18#include "absl/strings/str_cat.h"
19#include "absl/strings/str_format.h"
20#include "absl/strings/string_view.h"
23#include "util/hex.h"
24#include "util/log.h"
25#include "util/macro.h"
26
27#ifdef __EMSCRIPTEN__
28#include <emscripten.h>
30#endif
31
32namespace yaze {
33
34namespace {
35
36// ============================================================================
37// ROM Structure Constants
38// ============================================================================
39
41constexpr size_t kBaseRomSize = 1048576;
42
44constexpr size_t kHeaderSize = 0x200; // 512 bytes
45
46// ============================================================================
47// SMC Header Detection and Removal
48// ============================================================================
49
50void MaybeStripSmcHeader(std::vector<uint8_t>& rom_data, unsigned long& size) {
51 if (size % kBaseRomSize == kHeaderSize && size >= kHeaderSize &&
52 rom_data.size() >= kHeaderSize) {
53 rom_data.erase(rom_data.begin(), rom_data.begin() + kHeaderSize);
54 size -= kHeaderSize;
55 LOG_INFO("Rom", "Stripped SMC header from ROM (new size: %lu)", size);
56 }
57}
58
59#ifdef __EMSCRIPTEN__
60inline void MaybeBroadcastChange(uint32_t offset,
61 const std::vector<uint8_t>& old_bytes,
62 const std::vector<uint8_t>& new_bytes) {
63 if (new_bytes.empty()) return;
64 auto& collab = app::platform::GetWasmCollaborationInstance();
65 if (!collab.IsConnected() || collab.IsApplyingRemoteChange()) {
66 return;
67 }
68 (void)collab.BroadcastChange(offset, old_bytes, new_bytes);
69}
70#endif
71
72} // namespace
73
74absl::Status Rom::LoadFromFile(const std::string& filename,
75 const LoadOptions& options) {
76 if (filename.empty()) {
77 return absl::InvalidArgumentError(
78 "Could not load ROM: parameter `filename` is empty.");
79 }
80
81#ifdef __EMSCRIPTEN__
83 std::ifstream test_file(filename_, std::ios::binary);
84 if (!test_file.is_open()) {
85 return absl::NotFoundError(
86 absl::StrCat("ROM file does not exist or cannot be opened: ", filename_));
87 }
88 test_file.seekg(0, std::ios::end);
89 size_ = test_file.tellg();
90 test_file.close();
91
92 if (size_ < 32768) {
93 return absl::InvalidArgumentError(absl::StrFormat(
94 "ROM file too small (%zu bytes), minimum is 32KB", size_));
95 }
96#else
97 if (!std::filesystem::exists(filename)) {
98 return absl::NotFoundError(
99 absl::StrCat("ROM file does not exist: ", filename));
100 }
101 filename_ = std::filesystem::absolute(filename).string();
102#endif
103 short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1);
104
105 std::ifstream file(filename_, std::ios::binary);
106 if (!file.is_open()) {
107 return absl::NotFoundError(
108 absl::StrCat("Could not open ROM file: ", filename_));
109 }
110
111#ifndef __EMSCRIPTEN__
112 try {
113 size_ = std::filesystem::file_size(filename_);
114 if (size_ < 32768) {
115 return absl::InvalidArgumentError(absl::StrFormat(
116 "ROM file too small (%zu bytes), minimum is 32KB", size_));
117 }
118 } catch (...) {
119 file.seekg(0, std::ios::end);
120 size_ = file.tellg();
121 }
122#endif
123
124 try {
125 rom_data_.resize(size_);
126 file.seekg(0, std::ios::beg);
127 file.read(reinterpret_cast<char*>(rom_data_.data()), size_);
128 } catch (const std::bad_alloc& e) {
129 return absl::ResourceExhaustedError(absl::StrFormat(
130 "Failed to allocate memory for ROM (%zu bytes)", size_));
131 }
132
133 file.close();
134
135 if (options.strip_header) {
136 MaybeStripSmcHeader(rom_data_, size_);
137 }
138 size_ = rom_data_.size();
139
140 if (options.load_resource_labels) {
141 resource_label_manager_.LoadLabels(absl::StrFormat("%s.labels", filename));
142 }
143
144 return absl::OkStatus();
145}
146
147absl::Status Rom::LoadFromData(const std::vector<uint8_t>& data,
148 const LoadOptions& options) {
149 if (data.empty()) {
150 return absl::InvalidArgumentError(
151 "Could not load ROM: parameter `data` is empty.");
152 }
153 rom_data_ = data;
154 size_ = data.size();
155
156 if (options.strip_header) {
157 MaybeStripSmcHeader(rom_data_, size_);
158 }
159 size_ = rom_data_.size();
160
161 return absl::OkStatus();
162}
163
164absl::Status Rom::SaveToFile(const SaveSettings& settings) {
165 if (rom_data_.empty()) {
166 return absl::InternalError("ROM data is empty.");
167 }
168
169 std::string filename = settings.filename;
170 if (filename.empty()) {
172 }
173
174 if (settings.backup) {
175 auto now = std::chrono::system_clock::now();
176 auto now_c = std::chrono::system_clock::to_time_t(now);
177 std::string backup_filename =
178 absl::StrCat(filename, "_backup_", std::ctime(&now_c));
179
180 backup_filename.erase(
181 std::remove(backup_filename.begin(), backup_filename.end(), '\n'),
182 backup_filename.end());
183 std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_');
184
185 try {
186 std::filesystem::copy(filename_, backup_filename,
187 std::filesystem::copy_options::overwrite_existing);
188 } catch (const std::filesystem::filesystem_error& e) {
189 LOG_WARN("Rom", "Could not create backup: %s", e.what());
190 }
191 }
192
193 if (settings.save_new) {
194 auto now = std::chrono::system_clock::now();
195 auto now_c = std::chrono::system_clock::to_time_t(now);
196 auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
197 filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c));
198 filename.erase(std::remove(filename.begin(), filename.end(), ' '),
199 filename.end());
200 filename.erase(std::remove(filename.begin(), filename.end(), '\n'),
201 filename.end());
202 filename = filename + ".sfc";
203 }
204
205 std::ofstream file(filename.data(), std::ios::binary | std::ios::trunc);
206 if (!file) {
207 return absl::InternalError(
208 absl::StrCat("Could not open ROM file for writing: ", filename));
209 }
210
211 file.write(reinterpret_cast<const char*>(rom_data_.data()), rom_data_.size());
212 if (!file) {
213 return absl::InternalError(
214 absl::StrCat("Error while writing to ROM file: ", filename));
215 }
216
217 dirty_ = false;
218 return absl::OkStatus();
219}
220
221absl::StatusOr<uint8_t> Rom::ReadByte(int offset) {
222 if (offset < 0 || offset >= static_cast<int>(rom_data_.size())) {
223 return absl::OutOfRangeError(absl::StrFormat("Offset %d out of range (size: %d)", offset, rom_data_.size()));
224 }
225 return rom_data_[offset];
226}
227
228absl::StatusOr<uint16_t> Rom::ReadWord(int offset) {
229 if (offset < 0 || offset + 1 >= static_cast<int>(rom_data_.size())) {
230 return absl::OutOfRangeError("Offset out of range");
231 }
232 return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8));
233}
234
235absl::StatusOr<uint32_t> Rom::ReadLong(int offset) {
236 if (offset < 0 || offset + 2 >= static_cast<int>(rom_data_.size())) {
237 return absl::OutOfRangeError("Offset out of range");
238 }
239 return (uint32_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8) |
240 (rom_data_[offset + 2] << 16));
241}
242
243absl::StatusOr<std::vector<uint8_t>> Rom::ReadByteVector(
244 uint32_t offset, uint32_t length) const {
245 if (offset + length > static_cast<uint32_t>(rom_data_.size())) {
246 return absl::OutOfRangeError("Offset and length out of range");
247 }
248 std::vector<uint8_t> result;
249 result.reserve(length);
250 for (uint32_t i = offset; i < offset + length; i++) {
251 result.push_back(rom_data_[i]);
252 }
253 return result;
254}
255
256absl::StatusOr<gfx::Tile16> Rom::ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr) {
257 // Skip 8 bytes per tile.
258 auto tpos = tile16_ptr + (tile16_id * 0x08);
259 gfx::Tile16 tile16 = {};
262 tpos += 2;
265 tpos += 2;
268 tpos += 2;
271 return tile16;
272}
273
274absl::Status Rom::WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16& tile) {
275 auto tpos = tile16_ptr + (tile16_id * 0x08);
277 tpos += 2;
279 tpos += 2;
281 tpos += 2;
283 return absl::OkStatus();
284}
285
286absl::Status Rom::WriteByte(int addr, uint8_t value) {
287 if (addr >= static_cast<int>(rom_data_.size())) {
288 return absl::OutOfRangeError("Address out of range");
289 }
290 const uint8_t old_val = rom_data_[addr];
291 rom_data_[addr] = value;
292 dirty_ = true;
293#ifdef __EMSCRIPTEN__
294 MaybeBroadcastChange(addr, {old_val}, {value});
295#endif
296 return absl::OkStatus();
297}
298
299absl::Status Rom::WriteWord(int addr, uint16_t value) {
300 if (addr + 1 >= static_cast<int>(rom_data_.size())) {
301 return absl::OutOfRangeError("Address out of range");
302 }
303 const uint8_t old0 = rom_data_[addr];
304 const uint8_t old1 = rom_data_[addr + 1];
305 rom_data_[addr] = (uint8_t)(value & 0xFF);
306 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
307 dirty_ = true;
308#ifdef __EMSCRIPTEN__
310 {static_cast<uint8_t>(value & 0xFF),
311 static_cast<uint8_t>((value >> 8) & 0xFF)});
312#endif
313 return absl::OkStatus();
314}
315
316absl::Status Rom::WriteShort(int addr, uint16_t value) {
317 return WriteWord(addr, value);
318}
319
320absl::Status Rom::WriteLong(uint32_t addr, uint32_t value) {
321 if (addr + 2 >= static_cast<uint32_t>(rom_data_.size())) {
322 return absl::OutOfRangeError("Address out of range");
323 }
324 const uint8_t old0 = rom_data_[addr];
325 const uint8_t old1 = rom_data_[addr + 1];
326 const uint8_t old2 = rom_data_[addr + 2];
327 rom_data_[addr] = (uint8_t)(value & 0xFF);
328 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
329 rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF);
330 dirty_ = true;
331#ifdef __EMSCRIPTEN__
333 {static_cast<uint8_t>(value & 0xFF),
334 static_cast<uint8_t>((value >> 8) & 0xFF),
335 static_cast<uint8_t>((value >> 16) & 0xFF)});
336#endif
337 return absl::OkStatus();
338}
339
340absl::Status Rom::WriteVector(int addr, std::vector<uint8_t> data) {
341 if (addr + static_cast<int>(data.size()) > static_cast<int>(rom_data_.size())) {
342 return absl::OutOfRangeError("Address out of range");
343 }
344 std::vector<uint8_t> old_data;
345 old_data.reserve(data.size());
346 for (int i = 0; i < static_cast<int>(data.size()); i++) {
347 old_data.push_back(rom_data_[addr + i]);
348 rom_data_[addr + i] = data[i];
349 }
350 dirty_ = true;
351#ifdef __EMSCRIPTEN__
352 MaybeBroadcastChange(addr, old_data, data);
353#endif
354 return absl::OkStatus();
355}
356
357absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor& color) {
358 uint16_t bgr = ((color.snes() >> 10) & 0x1F) | ((color.snes() & 0x1F) << 10) |
359 (color.snes() & 0x7C00);
360 return WriteWord(address, bgr);
361}
362
363absl::Status Rom::WriteHelper(const WriteAction& action) {
364 if (std::holds_alternative<uint8_t>(action.value)) {
365 return WriteByte(action.address, std::get<uint8_t>(action.value));
366 } else if (std::holds_alternative<uint16_t>(action.value) ||
367 std::holds_alternative<short>(action.value)) {
368 return WriteShort(action.address, std::get<uint16_t>(action.value));
369 } else if (std::holds_alternative<std::vector<uint8_t>>(action.value)) {
370 return WriteVector(action.address,
371 std::get<std::vector<uint8_t>>(action.value));
372 } else if (std::holds_alternative<gfx::SnesColor>(action.value)) {
373 return WriteColor(action.address, std::get<gfx::SnesColor>(action.value));
374 }
375 return absl::InvalidArgumentError("Invalid write argument type");
376}
377
378} // namespace yaze
379
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:243
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:74
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
Definition rom.cc:256
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:357
auto filename() const
Definition rom.h:141
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:286
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
Definition rom.cc:274
const auto & vector() const
Definition rom.h:139
absl::StatusOr< uint16_t > ReadWord(int offset)
Definition rom.cc:228
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:340
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:164
absl::StatusOr< uint32_t > ReadLong(int offset)
Definition rom.cc:235
std::vector< uint8_t > rom_data_
Definition rom.h:164
auto data() const
Definition rom.h:135
bool dirty_
Definition rom.h:170
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:147
std::string filename_
Definition rom.h:158
unsigned long size_
Definition rom.h:152
absl::StatusOr< uint8_t > ReadByte(int offset)
Definition rom.cc:221
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:316
project::ResourceLabelManager resource_label_manager_
Definition rom.h:167
std::string short_name_
Definition rom.h:161
absl::Status WriteWord(int addr, uint16_t value)
Definition rom.cc:299
virtual absl::Status WriteHelper(const WriteAction &action)
Definition rom.cc:363
absl::Status WriteLong(uint32_t addr, uint32_t value)
Definition rom.cc:320
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:140
#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
void MaybeStripSmcHeader(std::vector< uint8_t > &rom_data, unsigned long &size)
Definition rom.cc:50
constexpr size_t kHeaderSize
Size of the optional SMC/SFC copier header that some ROM dumps include.
Definition rom.cc:44
constexpr size_t kBaseRomSize
Standard SNES ROM size for The Legend of Zelda: A Link to the Past (1MB)
Definition rom.cc:41
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:304
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:321
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
bool load_resource_labels
Definition rom.h:34
ValueType value
Definition rom.h:99
bool LoadLabels(const std::string &filename)
Definition project.cc:1204