From 53f1fa81f300543cc780e195de46b1063c45896d Mon Sep 17 00:00:00 2001 From: scawful Date: Tue, 29 Apr 2025 13:47:00 -0400 Subject: [PATCH] Refactor ROM class; introduce snes.h for address conversion utilities, enhancing code organization and maintainability. --- src/app/rom.cc | 450 ++++++++++++++++++++++++------------------------- src/app/rom.h | 123 +++++--------- src/app/snes.h | 51 ++++++ 3 files changed, 317 insertions(+), 307 deletions(-) create mode 100644 src/app/snes.h diff --git a/src/app/rom.cc b/src/app/rom.cc index 33ceeb1c..bf0bb247 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -223,6 +223,231 @@ absl::Status Rom::LoadFromData(const std::vector &data, bool z3_load) { return absl::OkStatus(); } +absl::Status Rom::LoadZelda3() { + // Check if the ROM has a header + constexpr size_t kBaseRomSize = 1048576; // 1MB + constexpr size_t kHeaderSize = 0x200; // 512 bytes + if (size_ % kBaseRomSize == kHeaderSize) { + auto header = std::vector(rom_data_.begin(), + rom_data_.begin() + kHeaderSize); + rom_data_.erase(rom_data_.begin(), rom_data_.begin() + kHeaderSize); + size_ -= 0x200; + } + + // Copy ROM title + constexpr uint32_t kTitleStringOffset = 0x7FC0; + constexpr uint32_t kTitleStringLength = 20; + title_.resize(kTitleStringLength); + std::copy(rom_data_.begin() + kTitleStringOffset, + rom_data_.begin() + kTitleStringOffset + kTitleStringLength, + title_.begin()); + if (rom_data_[kTitleStringOffset + 0x19] == 0) { + version_ = zelda3_version::JP; + } else { + version_ = zelda3_version::US; + } + + // Load additional resources + RETURN_IF_ERROR(gfx::LoadAllPalettes(rom_data_, palette_groups_)); + // TODO Load gfx groups or expanded ZS values + RETURN_IF_ERROR(LoadGfxGroups()); + + // Expand the ROM data to 2MB without changing the data in the first 1MB + rom_data_.resize(kBaseRomSize * 2); + size_ = kBaseRomSize * 2; + + return absl::OkStatus(); +} + +absl::Status Rom::LoadGfxGroups() { + ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer)); + main_blockset_ptr = SnesToPc(main_blockset_ptr); + + for (uint32_t i = 0; i < kNumMainBlocksets; i++) { + for (int j = 0; j < 8; j++) { + main_blockset_ids[i][j] = rom_data_[main_blockset_ptr + (i * 8) + j]; + } + } + + for (uint32_t i = 0; i < kNumRoomBlocksets; i++) { + for (int j = 0; j < 4; j++) { + room_blockset_ids[i][j] = rom_data_[kEntranceGfxGroup + (i * 4) + j]; + } + } + + for (uint32_t i = 0; i < kNumSpritesets; i++) { + for (int j = 0; j < 4; j++) { + spriteset_ids[i][j] = + rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j]; + } + } + + for (uint32_t i = 0; i < kNumPalettesets; i++) { + for (int j = 0; j < 4; j++) { + paletteset_ids[i][j] = + rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j]; + } + } + + return absl::OkStatus(); +} + +absl::Status Rom::SaveGfxGroups() { + ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer)); + main_blockset_ptr = SnesToPc(main_blockset_ptr); + + for (uint32_t i = 0; i < kNumMainBlocksets; i++) { + for (int j = 0; j < 8; j++) { + rom_data_[main_blockset_ptr + (i * 8) + j] = main_blockset_ids[i][j]; + } + } + + for (uint32_t i = 0; i < kNumRoomBlocksets; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[kEntranceGfxGroup + (i * 4) + j] = room_blockset_ids[i][j]; + } + } + + for (uint32_t i = 0; i < kNumSpritesets; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j] = + spriteset_ids[i][j]; + } + } + + for (uint32_t i = 0; i < kNumPalettesets; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j] = + paletteset_ids[i][j]; + } + } + + return absl::OkStatus(); +} + +absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) { + absl::Status non_firing_status; + if (rom_data_.empty()) { + return absl::InternalError("ROM data is empty."); + } + + // Check if filename is empty + if (filename == "") { + filename = filename_; + } + + // Check if backup is enabled + if (backup) { + // Create a backup file with timestamp in its name + auto now = std::chrono::system_clock::now(); + auto now_c = std::chrono::system_clock::to_time_t(now); + std::string backup_filename = + absl::StrCat(filename, "_backup_", std::ctime(&now_c)); + + // Remove newline character from ctime() + backup_filename.erase( + std::remove(backup_filename.begin(), backup_filename.end(), '\n'), + backup_filename.end()); + + // Replace spaces with underscores + std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_'); + + // Now, copy the original file to the backup file + try { + std::filesystem::copy(filename_, backup_filename, + std::filesystem::copy_options::overwrite_existing); + } catch (const std::filesystem::filesystem_error &e) { + non_firing_status = absl::InternalError(absl::StrCat( + "Could not create backup file: ", backup_filename, " - ", e.what())); + } + } + + // Run the other save functions + if (core::FeatureFlags::get().kSaveAllPalettes) + RETURN_IF_ERROR(SaveAllPalettes()); + if (core::FeatureFlags::get().kSaveGfxGroups) + RETURN_IF_ERROR(SaveGfxGroups()); + + if (save_new) { + // Create a file of the same name and append the date between the filename + // and file extension + auto now = std::chrono::system_clock::now(); + auto now_c = std::chrono::system_clock::to_time_t(now); + auto filename_no_ext = filename.substr(0, filename.find_last_of(".")); + std::cout << filename_no_ext << std::endl; + filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c)); + // Remove spaces from new_filename and replace with _ + filename.erase(std::remove(filename.begin(), filename.end(), ' '), + filename.end()); + // Remove newline character from ctime() + filename.erase(std::remove(filename.begin(), filename.end(), '\n'), + filename.end()); + // Add the file extension back to the new_filename + filename = filename + ".sfc"; + std::cout << filename << std::endl; + } + + // Open the file that we know exists for writing + std::ofstream file(filename.data(), std::ios::binary | std::ios::app); + if (!file) { + // Create the file if it does not exist + file.open(filename.data(), std::ios::binary); + if (!file) { + return absl::InternalError( + absl::StrCat("Could not open or create ROM file: ", filename)); + } + } + + // Save the data to the file + try { + file.write( + static_cast(static_cast(rom_data_.data())), + rom_data_.size()); + } catch (const std::ofstream::failure &e) { + return absl::InternalError(absl::StrCat( + "Error while writing to ROM file: ", filename, " - ", e.what())); + } + + // Check for write errors + if (!file) { + return absl::InternalError( + absl::StrCat("Error while writing to ROM file: ", filename)); + } + + if (!non_firing_status.ok()) { + return non_firing_status; + } + + return absl::OkStatus(); +} + +absl::Status Rom::SavePalette(int index, const std::string &group_name, + gfx::SnesPalette &palette) { + for (size_t j = 0; j < palette.size(); ++j) { + gfx::SnesColor color = palette[j]; + // If the color is modified, save the color to the ROM + if (color.is_modified()) { + RETURN_IF_ERROR( + WriteColor(gfx::GetPaletteAddress(group_name, index, j), color)); + color.set_modified(false); // Reset the modified flag after saving + } + } + return absl::OkStatus(); +} + +absl::Status Rom::SaveAllPalettes() { + RETURN_IF_ERROR( + palette_groups_.for_each([&](gfx::PaletteGroup &group) -> absl::Status { + for (size_t i = 0; i < group.size(); ++i) { + RETURN_IF_ERROR( + SavePalette(i, group.name(), *group.mutable_palette(i))); + } + return absl::OkStatus(); + })); + + return absl::OkStatus(); +} + absl::StatusOr Rom::ReadByte(int offset) { if (offset >= static_cast(rom_data_.size())) { return absl::FailedPreconditionError("Offset out of range"); @@ -360,231 +585,6 @@ absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor &color) { return WriteShort(address, bgr); } -absl::Status Rom::LoadZelda3() { - // Check if the ROM has a header - constexpr size_t kBaseRomSize = 1048576; // 1MB - constexpr size_t kHeaderSize = 0x200; // 512 bytes - if (size_ % kBaseRomSize == kHeaderSize) { - auto header = std::vector(rom_data_.begin(), - rom_data_.begin() + kHeaderSize); - rom_data_.erase(rom_data_.begin(), rom_data_.begin() + kHeaderSize); - size_ -= 0x200; - } - - // Copy ROM title - constexpr uint32_t kTitleStringOffset = 0x7FC0; - constexpr uint32_t kTitleStringLength = 20; - title_.resize(kTitleStringLength); - std::copy(rom_data_.begin() + kTitleStringOffset, - rom_data_.begin() + kTitleStringOffset + kTitleStringLength, - title_.begin()); - if (rom_data_[kTitleStringOffset + 0x19] == 0) { - version_ = zelda3_version::JP; - } else { - version_ = zelda3_version::US; - } - - // Load additional resources - RETURN_IF_ERROR(gfx::LoadAllPalettes(rom_data_, palette_groups_)); - // TODO Load gfx groups or expanded ZS values - RETURN_IF_ERROR(LoadGfxGroups()); - - // Expand the ROM data to 2MB without changing the data in the first 1MB - rom_data_.resize(kBaseRomSize * 2); - size_ = kBaseRomSize * 2; - - return absl::OkStatus(); -} - -absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) { - absl::Status non_firing_status; - if (rom_data_.empty()) { - return absl::InternalError("ROM data is empty."); - } - - // Check if filename is empty - if (filename == "") { - filename = filename_; - } - - // Check if backup is enabled - if (backup) { - // Create a backup file with timestamp in its name - auto now = std::chrono::system_clock::now(); - auto now_c = std::chrono::system_clock::to_time_t(now); - std::string backup_filename = - absl::StrCat(filename, "_backup_", std::ctime(&now_c)); - - // Remove newline character from ctime() - backup_filename.erase( - std::remove(backup_filename.begin(), backup_filename.end(), '\n'), - backup_filename.end()); - - // Replace spaces with underscores - std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_'); - - // Now, copy the original file to the backup file - try { - std::filesystem::copy(filename_, backup_filename, - std::filesystem::copy_options::overwrite_existing); - } catch (const std::filesystem::filesystem_error &e) { - non_firing_status = absl::InternalError(absl::StrCat( - "Could not create backup file: ", backup_filename, " - ", e.what())); - } - } - - // Run the other save functions - if (core::FeatureFlags::get().kSaveAllPalettes) - RETURN_IF_ERROR(SaveAllPalettes()); - if (core::FeatureFlags::get().kSaveGfxGroups) - RETURN_IF_ERROR(SaveGfxGroups()); - - if (save_new) { - // Create a file of the same name and append the date between the filename - // and file extension - auto now = std::chrono::system_clock::now(); - auto now_c = std::chrono::system_clock::to_time_t(now); - auto filename_no_ext = filename.substr(0, filename.find_last_of(".")); - std::cout << filename_no_ext << std::endl; - filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c)); - // Remove spaces from new_filename and replace with _ - filename.erase(std::remove(filename.begin(), filename.end(), ' '), - filename.end()); - // Remove newline character from ctime() - filename.erase(std::remove(filename.begin(), filename.end(), '\n'), - filename.end()); - // Add the file extension back to the new_filename - filename = filename + ".sfc"; - std::cout << filename << std::endl; - } - - // Open the file that we know exists for writing - std::ofstream file(filename.data(), std::ios::binary | std::ios::app); - if (!file) { - // Create the file if it does not exist - file.open(filename.data(), std::ios::binary); - if (!file) { - return absl::InternalError( - absl::StrCat("Could not open or create ROM file: ", filename)); - } - } - - // Save the data to the file - try { - file.write( - static_cast(static_cast(rom_data_.data())), - rom_data_.size()); - } catch (const std::ofstream::failure &e) { - return absl::InternalError(absl::StrCat( - "Error while writing to ROM file: ", filename, " - ", e.what())); - } - - // Check for write errors - if (!file) { - return absl::InternalError( - absl::StrCat("Error while writing to ROM file: ", filename)); - } - - if (!non_firing_status.ok()) { - return non_firing_status; - } - - return absl::OkStatus(); -} - -absl::Status Rom::SavePalette(int index, const std::string &group_name, - gfx::SnesPalette &palette) { - for (size_t j = 0; j < palette.size(); ++j) { - gfx::SnesColor color = palette[j]; - // If the color is modified, save the color to the ROM - if (color.is_modified()) { - RETURN_IF_ERROR( - WriteColor(gfx::GetPaletteAddress(group_name, index, j), color)); - color.set_modified(false); // Reset the modified flag after saving - } - } - return absl::OkStatus(); -} - -absl::Status Rom::SaveAllPalettes() { - RETURN_IF_ERROR( - palette_groups_.for_each([&](gfx::PaletteGroup &group) -> absl::Status { - for (size_t i = 0; i < group.size(); ++i) { - RETURN_IF_ERROR( - SavePalette(i, group.name(), *group.mutable_palette(i))); - } - return absl::OkStatus(); - })); - - return absl::OkStatus(); -} - -absl::Status Rom::LoadGfxGroups() { - ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer)); - main_blockset_ptr = SnesToPc(main_blockset_ptr); - - for (uint32_t i = 0; i < kNumMainBlocksets; i++) { - for (int j = 0; j < 8; j++) { - main_blockset_ids[i][j] = rom_data_[main_blockset_ptr + (i * 8) + j]; - } - } - - for (uint32_t i = 0; i < kNumRoomBlocksets; i++) { - for (int j = 0; j < 4; j++) { - room_blockset_ids[i][j] = rom_data_[kEntranceGfxGroup + (i * 4) + j]; - } - } - - for (uint32_t i = 0; i < kNumSpritesets; i++) { - for (int j = 0; j < 4; j++) { - spriteset_ids[i][j] = - rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j]; - } - } - - for (uint32_t i = 0; i < kNumPalettesets; i++) { - for (int j = 0; j < 4; j++) { - paletteset_ids[i][j] = - rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j]; - } - } - - return absl::OkStatus(); -} - -absl::Status Rom::SaveGfxGroups() { - ASSIGN_OR_RETURN(auto main_blockset_ptr, ReadWord(kGfxGroupsPointer)); - main_blockset_ptr = SnesToPc(main_blockset_ptr); - - for (uint32_t i = 0; i < kNumMainBlocksets; i++) { - for (int j = 0; j < 8; j++) { - rom_data_[main_blockset_ptr + (i * 8) + j] = main_blockset_ids[i][j]; - } - } - - for (uint32_t i = 0; i < kNumRoomBlocksets; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[kEntranceGfxGroup + (i * 4) + j] = room_blockset_ids[i][j]; - } - } - - for (uint32_t i = 0; i < kNumSpritesets; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j] = - spriteset_ids[i][j]; - } - } - - for (uint32_t i = 0; i < kNumPalettesets; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j] = - paletteset_ids[i][j]; - } - } - - return absl::OkStatus(); -} - std::shared_ptr SharedRom::shared_rom_ = nullptr; } // namespace yaze diff --git a/src/app/rom.h b/src/app/rom.h index da9b475e..198e4ee4 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -22,6 +22,7 @@ #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" +#include "app/snes.h" #include "util/macro.h" namespace yaze { @@ -116,50 +117,14 @@ class Rom { return status; } - uint8_t& operator[](unsigned long i) { - if (i >= size_) throw std::out_of_range("Rom index out of range"); - return rom_data_[i]; - } - - bool is_loaded() const { return !rom_data_.empty(); } - auto title() const { return title_; } - auto size() const { return size_; } - auto data() const { return rom_data_.data(); } - auto mutable_data() { return rom_data_.data(); } - auto begin() { return rom_data_.begin(); } - auto end() { return rom_data_.end(); } - - auto vector() const { return rom_data_; } - auto filename() const { return filename_; } - auto set_filename(std::string name) { filename_ = name; } - auto short_name() const { return short_name_; } - auto graphics_buffer() const { return graphics_buffer_; } - auto mutable_graphics_buffer() { return &graphics_buffer_; } - auto palette_group() const { return palette_groups_; } - auto mutable_palette_group() { return &palette_groups_; } - auto dungeon_palette(int i) { return palette_groups_.dungeon_main[i]; } - auto mutable_dungeon_palette(int i) { - return palette_groups_.dungeon_main.mutable_palette(i); - } - - ResourceLabelManager* resource_label() { return &resource_label_manager_; } - zelda3_version_pointers version_constants() const { - return kVersionConstantsMap.at(version_); - } - - std::array, kNumMainBlocksets> main_blockset_ids; - std::array, kNumRoomBlocksets> room_blockset_ids; - std::array, kNumSpritesets> spriteset_ids; - std::array, kNumPalettesets> paletteset_ids; - struct WriteAction { + using ValueType = + std::variant, + gfx::SnesColor, std::vector>; int address; - std::variant, - gfx::SnesColor, std::vector> - value; + ValueType value; }; - private: virtual absl::Status WriteHelper(const WriteAction& action) { if (std::holds_alternative(action.value)) { return WriteByte(action.address, std::get(action.value)); @@ -196,6 +161,42 @@ class Rom { return absl::OkStatus(); } + uint8_t& operator[](unsigned long i) { + if (i >= size_) throw std::out_of_range("Rom index out of range"); + return rom_data_[i]; + } + + bool is_loaded() const { return !rom_data_.empty(); } + auto title() const { return title_; } + auto size() const { return size_; } + auto data() const { return rom_data_.data(); } + auto mutable_data() { return rom_data_.data(); } + auto begin() { return rom_data_.begin(); } + auto end() { return rom_data_.end(); } + auto vector() const { return rom_data_; } + auto filename() const { return filename_; } + auto set_filename(std::string name) { filename_ = name; } + auto short_name() const { return short_name_; } + auto graphics_buffer() const { return graphics_buffer_; } + auto mutable_graphics_buffer() { return &graphics_buffer_; } + auto palette_group() const { return palette_groups_; } + auto mutable_palette_group() { return &palette_groups_; } + auto dungeon_palette(int i) { return palette_groups_.dungeon_main[i]; } + auto mutable_dungeon_palette(int i) { + return palette_groups_.dungeon_main.mutable_palette(i); + } + + ResourceLabelManager* resource_label() { return &resource_label_manager_; } + zelda3_version_pointers version_constants() const { + return kVersionConstantsMap.at(version_); + } + + std::array, kNumMainBlocksets> main_blockset_ids; + std::array, kNumRoomBlocksets> room_blockset_ids; + std::array, kNumSpritesets> spriteset_ids; + std::array, kNumPalettesets> paletteset_ids; + + private: // Size of the ROM data. unsigned long size_ = 0; @@ -281,48 +282,6 @@ absl::StatusOr> Load2BppGraphics(const Rom& rom); absl::StatusOr> LoadLinkGraphics( const Rom& rom); -constexpr uint32_t kFastRomRegion = 0x808000; - -inline uint32_t SnesToPc(uint32_t addr) noexcept { - if (addr >= kFastRomRegion) { - addr -= kFastRomRegion; - } - uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000); - return (temp + 0x0); -} - -inline uint32_t PcToSnes(uint32_t addr) { - uint8_t* b = reinterpret_cast(&addr); - b[2] = static_cast(b[2] * 2); - - if (b[1] >= 0x80) { - b[2] += 1; - } else { - b[1] += 0x80; - } - - return addr; -} - -inline uint32_t Get24LocalFromPC(uint8_t* data, int addr, bool pc = true) { - uint32_t ret = - (PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr]; - if (pc) { - return SnesToPc(ret); - } - return ret; -} - -inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept { - return (bank << 16) | (high << 8) | low; -} - -inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept { - uint32_t result = 0; - result = (bank << 16) | addr; - return result; -} - /** * @brief A class to hold a shared pointer to a Rom object. */ diff --git a/src/app/snes.h b/src/app/snes.h new file mode 100644 index 00000000..8354b160 --- /dev/null +++ b/src/app/snes.h @@ -0,0 +1,51 @@ +#ifndef YAZE_SNES_H_ +#define YAZE_SNES_H_ + +#include + +namespace yaze { + +inline uint32_t SnesToPc(uint32_t addr) noexcept { + constexpr uint32_t kFastRomRegion = 0x808000; + if (addr >= kFastRomRegion) { + addr -= kFastRomRegion; + } + uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000); + return (temp + 0x0); +} + +inline uint32_t PcToSnes(uint32_t addr) { + uint8_t* b = reinterpret_cast(&addr); + b[2] = static_cast(b[2] * 2); + + if (b[1] >= 0x80) { + b[2] += 1; + } else { + b[1] += 0x80; + } + + return addr; +} + +inline uint32_t Get24LocalFromPC(uint8_t* data, int addr, bool pc = true) { + uint32_t ret = + (PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr]; + if (pc) { + return SnesToPc(ret); + } + return ret; +} + +inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept { + return (bank << 16) | (high << 8) | low; +} + +inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept { + uint32_t result = 0; + result = (bank << 16) | addr; + return result; +} + +} // namespace yaze + +#endif // YAZE_SNES_H_