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