yaze 0.2.0
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 <stack>
12#include <string>
13#include <unordered_map>
14#include <utility>
15#include <vector>
16
17#include "absl/container/flat_hash_map.h"
18#include "absl/status/status.h"
19#include "absl/status/statusor.h"
20#include "absl/strings/str_cat.h"
21#include "absl/strings/string_view.h"
22#include "app/core/constants.h"
24#include "app/gfx/bitmap.h"
25#include "app/gfx/compression.h"
26#include "app/gfx/snes_color.h"
28#include "app/gfx/snes_tile.h"
29
30namespace yaze {
31namespace app {
32
33using core::Renderer;
34constexpr int Uncompressed3BPPSize = 0x0600;
35constexpr int kEntranceGfxGroup = 0x5D97;
36
37namespace {
38int GetGraphicsAddress(const uchar* data, uint8_t addr, uint32_t ptr1,
39 uint32_t ptr2, uint32_t ptr3) {
41 data[ptr1 + addr], data[ptr2 + addr], data[ptr3 + addr]));
42}
43} // namespace
44
45absl::StatusOr<std::vector<uint8_t>> Rom::Load2BppGraphics() {
46 std::vector<uint8_t> sheet;
47 const uint8_t sheets[] = {113, 114, 218, 219, 220, 221};
48
49 for (const auto& sheet_id : sheets) {
50 auto offset = GetGraphicsAddress(data(), sheet_id,
51 version_constants().kOverworldGfxPtr1,
52 version_constants().kOverworldGfxPtr2,
53 version_constants().kOverworldGfxPtr3);
54 ASSIGN_OR_RETURN(auto decomp_sheet,
56 auto converted_sheet = gfx::SnesTo8bppSheet(decomp_sheet, 2);
57 for (const auto& each_pixel : converted_sheet) {
58 sheet.push_back(each_pixel);
59 }
60 }
61 return sheet;
62}
63
64absl::Status Rom::LoadLinkGraphics() {
65 const uint32_t kLinkGfxOffset = 0x80000; // $10:8000
66 const uint16_t kLinkGfxLength = 0x800; // 0x4000 or 0x7000?
67
68 // Load Links graphics from the ROM
69 for (uint32_t i = 0; i < kNumLinkSheets; i++) {
71 auto link_sheet_data,
72 ReadByteVector(/*offset=*/kLinkGfxOffset + (i * kLinkGfxLength),
73 /*length=*/kLinkGfxLength))
74 auto link_sheet_8bpp = gfx::SnesTo8bppSheet(link_sheet_data, /*bpp=*/4);
76 gfx::kTilesheetDepth, link_sheet_8bpp);
79 }
80 return absl::OkStatus();
81}
82
83absl::Status Rom::LoadAllGraphicsData(bool defer_render) {
84 std::vector<uint8_t> sheet;
85 bool bpp3 = false;
86
87 for (uint32_t i = 0; i < kNumGfxSheets; i++) {
88 if (i >= 115 && i <= 126) { // uncompressed sheets
89 sheet.resize(Uncompressed3BPPSize);
90 auto offset =
91 GetGraphicsAddress(data(), i, version_constants().kOverworldGfxPtr1,
92 version_constants().kOverworldGfxPtr2,
93 version_constants().kOverworldGfxPtr3);
94 for (int j = 0; j < Uncompressed3BPPSize; j++) {
95 sheet[j] = rom_data_[j + offset];
96 }
97 bpp3 = true;
98 } else if (i == 113 || i == 114 || i >= 218) {
99 bpp3 = false;
100 } else {
101 auto offset =
102 GetGraphicsAddress(data(), i, version_constants().kOverworldGfxPtr1,
103 version_constants().kOverworldGfxPtr2,
104 version_constants().kOverworldGfxPtr3);
105 ASSIGN_OR_RETURN(sheet,
106 gfx::lc_lz2::DecompressV2(rom_data_.data(), offset))
107 bpp3 = true;
108 }
109
110 if (bpp3) {
111 auto converted_sheet = gfx::SnesTo8bppSheet(sheet, 3);
113 gfx::kTilesheetDepth, converted_sheet);
114 if (graphics_sheets_[i].is_active()) {
115 if (i > 115) {
116 // Apply sprites palette
117 RETURN_IF_ERROR(graphics_sheets_[i].ApplyPaletteWithTransparent(
119 } else {
120 RETURN_IF_ERROR(graphics_sheets_[i].ApplyPaletteWithTransparent(
122 }
123 }
124
125 if (!defer_render) {
126 graphics_sheets_[i].CreateTexture(Renderer::GetInstance().renderer());
127 }
128
129 for (int j = 0; j < graphics_sheets_[i].size(); ++j) {
130 graphics_buffer_.push_back(graphics_sheets_[i].at(j));
131 }
132
133 } else {
134 for (int j = 0; j < graphics_sheets_[0].size(); ++j) {
135 graphics_buffer_.push_back(0xFF);
136 }
137 }
138 }
139 return absl::OkStatus();
140}
141
142absl::Status Rom::LoadFromFile(const std::string& filename, bool z3_load) {
143 if (filename.empty()) {
144 return absl::InvalidArgumentError(
145 "Could not load ROM: parameter `filename` is empty.");
146 }
147 std::string full_filename = std::filesystem::absolute(filename).string();
148 filename_ = full_filename;
149
150 // Open file
151 std::ifstream file(filename_, std::ios::binary);
152 if (!file.is_open()) {
153 return absl::NotFoundError(
154 absl::StrCat("Could not open ROM file: ", filename_));
155 }
156
157 // Get file size and resize rom_data_
158 try {
159 size_ = std::filesystem::file_size(filename_);
160 } catch (const std::filesystem::filesystem_error& e) {
161 // Try to get the file size from the open file stream
162 file.seekg(0, std::ios::end);
163 if (!file) {
164 return absl::InternalError(absl::StrCat(
165 "Could not get file size: ", filename_, " - ", e.what()));
166 }
167 size_ = file.tellg();
168 }
169 rom_data_.resize(size_);
170
171 // Read file into rom_data_
172 file.read(reinterpret_cast<char*>(rom_data_.data()), size_);
173
174 // Close file
175 file.close();
176
177 // Set is_loaded_ flag and return success
178 is_loaded_ = true;
179
180 if (z3_load) {
182 }
183
184 // Set up the resource labels
185 std::string resource_label_filename = absl::StrFormat("%s.labels", filename);
186 resource_label_manager_.LoadLabels(resource_label_filename);
187
188 return absl::OkStatus();
189}
190
191absl::Status Rom::LoadFromPointer(uchar* data, size_t length, bool z3_load) {
192 if (!data || length == 0)
193 return absl::InvalidArgumentError(
194 "Could not load ROM: parameter `data` is empty.");
195
197 if (rom_data_.size() < length) rom_data_.resize(length);
198
199 std::copy(data, data + length, rom_data_.begin());
200 size_ = length;
201 is_loaded_ = true;
202
203 if (z3_load) {
205 }
206 return absl::OkStatus();
207}
208
209absl::Status Rom::LoadZelda3() {
210 // Check if the ROM has a header
211 constexpr size_t kBaseRomSize = 1048576; // 1MB
212 constexpr size_t kHeaderSize = 0x200; // 512 bytes
213 if (size_ % kBaseRomSize == kHeaderSize) {
214 auto header =
215 std::vector<uchar>(rom_data_.begin(), rom_data_.begin() + 0x200);
216 rom_data_.erase(rom_data_.begin(), rom_data_.begin() + 0x200);
217 size_ -= 0x200;
218 }
219
220 // Copy ROM title
221 constexpr uint32_t kTitleStringOffset = 0x7FC0;
222 constexpr uint32_t kTitleStringLength = 20;
223 std::copy(rom_data_.begin() + kTitleStringOffset,
224 rom_data_.begin() + kTitleStringOffset + kTitleStringLength,
225 title_.begin());
226 if (rom_data_[kTitleStringOffset + 0x19] == 0) {
228 } else {
230 }
231
232 // Load additional resources
235
236 // Expand the ROM data to 2MB without changing the data in the first 1MB
237 rom_data_.resize(kBaseRomSize * 2);
238 size_ = kBaseRomSize * 2;
239
240 return absl::OkStatus();
241}
242
243absl::Status Rom::LoadFromBytes(const std::vector<uint8_t>& data) {
244 if (data.empty()) {
245 return absl::InvalidArgumentError(
246 "Could not load ROM: parameter `data` is empty.");
247 }
248 rom_data_ = data;
249 size_ = data.size();
250 is_loaded_ = true;
251 return absl::OkStatus();
252}
253
254absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) {
255 absl::Status non_firing_status;
256 if (rom_data_.empty()) {
257 return absl::InternalError("ROM data is empty.");
258 }
259
260 // Check if filename is empty
261 if (filename == "") {
263 }
264
265 // Check if backup is enabled
266 if (backup) {
267 // Create a backup file with timestamp in its name
268 auto now = std::chrono::system_clock::now();
269 auto now_c = std::chrono::system_clock::to_time_t(now);
270 std::string backup_filename =
271 absl::StrCat(filename, "_backup_", std::ctime(&now_c));
272
273 // Remove newline character from ctime()
274 backup_filename.erase(
275 std::remove(backup_filename.begin(), backup_filename.end(), '\n'),
276 backup_filename.end());
277
278 // Replace spaces with underscores
279 std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_');
280
281 // Now, copy the original file to the backup file
282 try {
283 std::filesystem::copy(filename_, backup_filename,
284 std::filesystem::copy_options::overwrite_existing);
285 } catch (const std::filesystem::filesystem_error& e) {
286 non_firing_status = absl::InternalError(absl::StrCat(
287 "Could not create backup file: ", backup_filename, " - ", e.what()));
288 }
289 }
290
291 // Run the other save functions
292 if (flags()->kSaveAllPalettes) {
294 }
295
296 if (flags()->kSaveGfxGroups) {
298 }
299
300 if (save_new) {
301 // Create a file of the same name and append the date between the filename
302 // and file extension
303 auto now = std::chrono::system_clock::now();
304 auto now_c = std::chrono::system_clock::to_time_t(now);
305 auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
306 std::cout << filename_no_ext << std::endl;
307 filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c));
308 // Remove spaces from new_filename and replace with _
309 filename.erase(std::remove(filename.begin(), filename.end(), ' '),
310 filename.end());
311 // Remove newline character from ctime()
312 filename.erase(std::remove(filename.begin(), filename.end(), '\n'),
313 filename.end());
314 // Add the file extension back to the new_filename
315 filename = filename + ".sfc";
316 std::cout << filename << std::endl;
317 }
318
319 // Open the file that we know exists for writing
320 std::ofstream file(filename.data(), std::ios::binary | std::ios::app);
321 if (!file) {
322 // Create the file if it does not exist
323 file.open(filename.data(), std::ios::binary);
324 if (!file) {
325 return absl::InternalError(
326 absl::StrCat("Could not open or create ROM file: ", filename));
327 }
328 }
329
330 // Save the data to the file
331 try {
332 file.write(
333 static_cast<const char*>(static_cast<const void*>(rom_data_.data())),
334 rom_data_.size());
335 } catch (const std::ofstream::failure& e) {
336 return absl::InternalError(absl::StrCat(
337 "Error while writing to ROM file: ", filename, " - ", e.what()));
338 }
339
340 // Check for write errors
341 if (!file) {
342 return absl::InternalError(
343 absl::StrCat("Error while writing to ROM file: ", filename));
344 }
345
346 if (!non_firing_status.ok()) {
347 return non_firing_status;
348 }
349
350 return absl::OkStatus();
351}
352
353absl::Status Rom::SavePalette(int index, const std::string& group_name,
354 gfx::SnesPalette& palette) {
355 for (size_t j = 0; j < palette.size(); ++j) {
356 gfx::SnesColor color = palette[j];
357 // If the color is modified, save the color to the ROM
358 if (color.is_modified()) {
360 WriteColor(gfx::GetPaletteAddress(group_name, index, j), color));
361 color.set_modified(false); // Reset the modified flag after saving
362 }
363 }
364 return absl::OkStatus();
365}
366
367absl::Status Rom::SaveAllPalettes() {
369 palette_groups_.for_each([&](gfx::PaletteGroup& group) -> absl::Status {
370 for (size_t i = 0; i < group.size(); ++i) {
371 RETURN_IF_ERROR(
372 SavePalette(i, group.name(), *group.mutable_palette(i)));
373 }
374 return absl::OkStatus();
375 }));
376
377 return absl::OkStatus();
378}
379
380absl::Status Rom::LoadGfxGroups() {
381 main_blockset_ids.resize(kNumMainBlocksets, std::vector<uint8_t>(8));
382 room_blockset_ids.resize(kNumRoomBlocksets, std::vector<uint8_t>(4));
383 spriteset_ids.resize(kNumSpritesets, std::vector<uint8_t>(4));
384 paletteset_ids.resize(kNumPalettesets, std::vector<uint8_t>(4));
385
386 ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer));
387 main_blockset_ptr = core::SnesToPc(main_blockset_ptr);
388
389 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
390 for (int j = 0; j < 8; j++) {
391 main_blockset_ids[i][j] = rom_data_[main_blockset_ptr + (i * 8) + j];
392 }
393 }
394
395 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
396 for (int j = 0; j < 4; j++) {
397 room_blockset_ids[i][j] = rom_data_[kEntranceGfxGroup + (i * 4) + j];
398 }
399 }
400
401 for (uint32_t i = 0; i < kNumSpritesets; i++) {
402 for (int j = 0; j < 4; j++) {
403 spriteset_ids[i][j] =
404 rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j];
405 }
406 }
407
408 for (uint32_t i = 0; i < kNumPalettesets; i++) {
409 for (int j = 0; j < 4; j++) {
410 paletteset_ids[i][j] =
411 rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j];
412 }
413 }
414
415 return absl::OkStatus();
416}
417
418absl::Status Rom::SaveGroupsToRom() {
419 ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer));
420 main_blockset_ptr = core::SnesToPc(main_blockset_ptr);
421
422 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
423 for (int j = 0; j < 8; j++) {
424 rom_data_[main_blockset_ptr + (i * 8) + j] = main_blockset_ids[i][j];
425 }
426 }
427
428 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
429 for (int j = 0; j < 4; j++) {
430 rom_data_[kEntranceGfxGroup + (i * 4) + j] = room_blockset_ids[i][j];
431 }
432 }
433
434 for (uint32_t i = 0; i < kNumSpritesets; i++) {
435 for (int j = 0; j < 4; j++) {
436 rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j] =
437 spriteset_ids[i][j];
438 }
439 }
440
441 for (uint32_t i = 0; i < kNumPalettesets; i++) {
442 for (int j = 0; j < 4; j++) {
443 rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j] =
444 paletteset_ids[i][j];
445 }
446 }
447
448 return absl::OkStatus();
449}
450
451std::shared_ptr<Rom> SharedRom::shared_rom_ = nullptr;
452
453} // namespace app
454} // namespace yaze
std::string title_
Definition rom.h:555
VersionConstants version_constants() const
Definition rom.h:491
Z3_Version version_
Definition rom.h:579
std::vector< uint8_t > rom_data_
Definition rom.h:561
absl::Status LoadGfxGroups()
Definition rom.cc:380
absl::Status SaveToFile(bool backup, bool save_new=false, std::string filename="")
Saves the Rom data to a file.
Definition rom.cc:254
absl::Status SaveAllPalettes()
Saves all palettes in the Rom.
Definition rom.cc:367
std::array< gfx::Bitmap, kNumGfxSheets > graphics_sheets_
Definition rom.h:567
auto filename() const
Definition rom.h:473
absl::Status SavePalette(int index, const std::string &group_name, gfx::SnesPalette &palette)
Definition rom.cc:353
absl::Status LoadAllGraphicsData(bool defer_render=false)
This function iterates over all graphics sheets in the Rom and loads them into memory....
Definition rom.cc:83
absl::StatusOr< std::vector< uint8_t > > Load2BppGraphics()
Loads 2bpp graphics from Rom data.
Definition rom.cc:45
absl::Status LoadLinkGraphics()
Loads the players 4bpp graphics sheet from Rom data.
Definition rom.cc:64
gfx::PaletteGroupMap palette_groups_
Definition rom.h:576
unsigned long size_
Definition rom.h:552
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.h:414
absl::Status SaveGroupsToRom()
Definition rom.cc:418
std::string filename_
Definition rom.h:558
auto data()
Definition rom.h:470
bool is_loaded_
Definition rom.h:549
absl::Status LoadFromFile(const std::string &filename, bool z3_load=true)
Definition rom.cc:142
absl::Status LoadFromBytes(const std::vector< uint8_t > &data)
Definition rom.cc:243
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length)
Definition rom.h:287
std::vector< uint8_t > graphics_buffer_
Definition rom.h:564
core::ResourceLabelManager resource_label_manager_
Definition rom.h:573
absl::Status LoadFromPointer(uchar *data, size_t length, bool z3_load=true)
Definition rom.cc:191
absl::Status LoadZelda3()
Definition rom.cc:209
std::array< gfx::Bitmap, kNumLinkSheets > link_graphics_
Definition rom.h:570
static Renderer & GetInstance()
Definition renderer.h:30
void RenderBitmap(gfx::Bitmap *bitmap)
Used to render a bitmap to the screen.
Definition renderer.h:52
SNES Color container.
Definition snes_color.h:38
void set_modified(bool m)
Definition snes_color.h:94
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
#define RETURN_IF_ERROR(expression)
Definition constants.h:69
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition constants.h:77
unsigned char uchar
Definition constants.h:121
int GetGraphicsAddress(const uchar *data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3)
Definition rom.cc:38
int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept
Definition common.h:244
uint32_t SnesToPc(uint32_t addr) noexcept
Definition common.h:223
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uchar *data, int offset, int size, int mode)
Decompresses a buffer of data using the LC_LZ2 algorithm.
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
constexpr int kTilesheetDepth
Definition snes_tile.h:16
constexpr int kTilesheetWidth
Definition snes_tile.h:14
std::vector< uint8_t > SnesTo8bppSheet(const std::vector< uint8_t > &sheet, int bpp)
Definition snes_tile.cc:152
absl::Status LoadAllPalettes(const std::vector< uint8_t > &rom_data, PaletteGroupMap &groups)
Loads all the palettes for the game.
constexpr int kTilesheetHeight
Definition snes_tile.h:15
constexpr int kEntranceGfxGroup
Definition rom.cc:35
constexpr uint32_t kNumRoomBlocksets
Definition rom.h:134
constexpr uint32_t kNumSpritesets
Definition rom.h:135
constexpr int Uncompressed3BPPSize
Definition rom.cc:34
constexpr uint32_t kNumLinkSheets
Definition rom.h:127
constexpr uint32_t kNumGfxSheets
Definition rom.h:126
constexpr uint32_t kNumMainBlocksets
Definition rom.h:133
constexpr uint32_t kGfxGroupsPointer
Definition rom.h:132
constexpr uint32_t kNumPalettesets
Definition rom.h:136
Definition common.cc:21
bool LoadLabels(const std::string &filename)
Definition labeling.cc:51
absl::Status for_each(Func &&func)
Represents a group of palettes.