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