From 6a0dc078c198bcc9b96065bbac669e2df3b6035c Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 22 Oct 2023 03:23:26 -0400 Subject: [PATCH] Refactor ROM class, add RunTransaction --- src/app/gfx/snes_palette.cc | 74 +++++++ src/app/gfx/snes_palette.h | 380 ++++++++++++++++++++++++++++++-- src/app/gfx/snes_tile.h | 14 ++ src/app/rom.cc | 423 ++++++++++++++++++++---------------- src/app/rom.h | 218 +++++++++++++------ src/app/zelda3/overworld.cc | 90 ++++---- src/app/zelda3/overworld.h | 2 +- src/cli/command_handler.cc | 58 ++++- src/cli/command_handler.h | 8 +- 9 files changed, 940 insertions(+), 327 deletions(-) diff --git a/src/app/gfx/snes_palette.cc b/src/app/gfx/snes_palette.cc index 9f04138f..dfc72862 100644 --- a/src/app/gfx/snes_palette.cc +++ b/src/app/gfx/snes_palette.cc @@ -10,12 +10,42 @@ #include #include +#include "absl/container/flat_hash_map.h" // for flat_hash_map +#include "absl/status/status.h" // for Status #include "app/core/constants.h" namespace yaze { namespace app { namespace gfx { +// Define a hash map to hold the addresses of different palette groups +const absl::flat_hash_map paletteGroupAddresses = { + {"ow_main", core::overworldPaletteMain}, + {"ow_aux", core::overworldPaletteAuxialiary}, + {"ow_animated", core::overworldPaletteAnimated}, + {"hud", core::hudPalettes}, + {"global_sprites", core::globalSpritePalettesLW}, + {"armors", core::armorPalettes}, + {"swords", core::swordPalettes}, + {"shields", core::shieldPalettes}, + {"sprites_aux1", core::spritePalettesAux1}, + {"sprites_aux2", core::spritePalettesAux2}, + {"sprites_aux3", core::spritePalettesAux3}, + {"dungeon_main", core::dungeonMainPalettes}, + {"grass", core::hardcodedGrassLW}, + {"3d_object", core::triforcePalette}, + {"ow_mini_map", core::overworldMiniMapPalettes}, +}; + +// Define a hash map to hold the number of colors in each palette group +const absl::flat_hash_map paletteGroupColorCounts = { + {"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7}, + {"hud", 32}, {"global_sprites", 60}, {"armors", 15}, + {"swords", 3}, {"shields", 4}, {"sprites_aux1", 7}, + {"sprites_aux2", 7}, {"sprites_aux3", 7}, {"dungeon_main", 90}, + {"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128}, +}; + constexpr uint16_t SNES_RED_MASK = 32; constexpr uint16_t SNES_GREEN_MASK = 32; constexpr uint16_t SNES_BLUE_MASK = 32; @@ -65,6 +95,16 @@ std::vector Convert(const std::vector& palette) { return data; } +SNESColor ReadColorFromROM(int offset, const uchar* rom) { + short color = (ushort)((rom[offset + 1]) << 8) | rom[offset]; + snes_color new_color; + new_color.red = (color & 0x1F) * 8; + new_color.green = ((color >> 5) & 0x1F) * 8; + new_color.blue = ((color >> 10) & 0x1F) * 8; + SNESColor snes_color(new_color); + return snes_color; +} + SNESColor GetCgxColor(uint16_t color) { ImVec4 rgb; rgb.x = (color & 0x1F) * 8; @@ -163,6 +203,40 @@ SDL_Palette* SNESPalette::GetSDL_Palette() { return sdl_palette.get(); } +SNESPalette ReadPaletteFromROM(int offset, int num_colors, const uchar* rom) { + int color_offset = 0; + std::vector colors(num_colors); + + while (color_offset < num_colors) { + short color = (ushort)((rom[offset + 1]) << 8) | rom[offset]; + gfx::snes_color new_color; + new_color.red = (color & 0x1F) * 8; + new_color.green = ((color >> 5) & 0x1F) * 8; + new_color.blue = ((color >> 10) & 0x1F) * 8; + colors[color_offset].SetSNES(ConvertRGBtoSNES(new_color)); + color_offset++; + offset += 2; + } + + gfx::SNESPalette palette(colors); + return palette; +} + +uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index, + size_t color_index) { + // Retrieve the base address for the palette group + uint32_t base_address = paletteGroupAddresses.at(group_name); + + // Retrieve the number of colors for each palette in the group + uint32_t colors_per_palette = paletteGroupColorCounts.at(group_name); + + // Calculate the address for thes specified color in the ROM + uint32_t address = base_address + (palette_index * colors_per_palette * 2) + + (color_index * 2); + + return address; +} + std::array ToFloatArray(const SNESColor& color) { std::array colorArray; colorArray[0] = color.GetRGB().x / 255.0f; diff --git a/src/app/gfx/snes_palette.h b/src/app/gfx/snes_palette.h index 10e70b12..695890d3 100644 --- a/src/app/gfx/snes_palette.h +++ b/src/app/gfx/snes_palette.h @@ -12,35 +12,83 @@ #include #include "absl/base/casts.h" +#include "absl/status/status.h" #include "app/core/constants.h" namespace yaze { namespace app { namespace gfx { +/** + * @brief Struct representing an SNES color. + */ struct snes_color { - uint16_t red; - uint16_t blue; - uint16_t green; + uint16_t red; /**< Red component of the color. */ + uint16_t blue; /**< Blue component of the color. */ + uint16_t green; /**< Green component of the color. */ }; using snes_color = struct snes_color; +/** + * @brief Struct representing an SNES palette. + */ struct snes_palette { - uint id; - uint size; - snes_color* colors; + uint id; /**< ID of the palette. */ + uint size; /**< Size of the palette. */ + snes_color* colors; /**< Pointer to the colors in the palette. */ }; using snes_palette = struct snes_palette; +/** + * @brief Converts an RGB color to an SNES color. + * + * @param color The RGB color to convert. + * @return The converted SNES color. + */ uint16_t ConvertRGBtoSNES(const snes_color& color); + +/** + * @brief Converts an SNES color to an RGB color. + * + * @param snes_color The SNES color to convert. + * @return The converted RGB color. + */ snes_color ConvertSNEStoRGB(uint16_t snes_color); + +/** + * @brief Extracts a vector of SNES colors from a data buffer. + * + * @param data The data buffer to extract from. + * @param offset The offset in the buffer to start extracting from. + * @param palette_size The size of the palette to extract. + * @return A vector of SNES colors extracted from the buffer. + */ std::vector Extract(const char* data, unsigned int offset, unsigned int palette_size); + +/** + * @brief Converts a vector of SNES colors to a vector of characters. + * + * @param palette The vector of SNES colors to convert. + * @return A vector of characters representing the converted SNES colors. + */ std::vector Convert(const std::vector& palette); +/** + * @brief Struct representing an SNES color with additional functionality. + */ struct SNESColor { + /** + * @brief Default constructor. + */ SNESColor() : rgb(0.f, 0.f, 0.f, 0.f), snes(0) {} + /** + * @brief Constructor that takes an ImVec4 value and converts it to an SNES + * color. + * + * @param val The ImVec4 value to convert. + */ explicit SNESColor(const ImVec4 val) : rgb(val) { snes_color color; color.red = val.x / 255; @@ -49,12 +97,29 @@ struct SNESColor { snes = ConvertRGBtoSNES(color); } + /** + * @brief Constructor that takes an SNES color and initializes the object with + * it. + * + * @param val The SNES color to initialize with. + */ explicit SNESColor(const snes_color val) : rgb(val.red, val.green, val.blue, 255.f), snes(ConvertRGBtoSNES(val)), rom_color(val) {} + /** + * @brief Gets the RGB value of the color. + * + * @return The RGB value of the color. + */ ImVec4 GetRGB() const { return rgb; } + + /** + * @brief Sets the RGB value of the color. + * + * @param val The RGB value to set. + */ void SetRGB(const ImVec4 val) { rgb.x = val.x / 255; rgb.y = val.y / 255; @@ -68,11 +133,25 @@ struct SNESColor { modified = true; } - // Used for indexed Bitmaps from CGX files + /** + * @brief Gets the RGB value of the color as it appears in the ROM. + * + * @return The RGB value of the color as it appears in the ROM. + */ snes_color GetRomRGB() const { return rom_color; } + /** + * @brief Gets the SNES value of the color. + * + * @return The SNES value of the color. + */ uint16_t GetSNES() const { return snes; } + /** + * @brief Sets the SNES value of the color. + * + * @param val The SNES value to set. + */ void SetSNES(uint16_t val) { snes = val; snes_color col = ConvertSNEStoRGB(val); @@ -80,25 +159,79 @@ struct SNESColor { modified = true; } + /** + * @brief Checks if the color has been modified. + * + * @return True if the color has been modified, false otherwise. + */ bool isModified() const { return modified; } + + /** + * @brief Checks if the color is transparent. + * + * @return True if the color is transparent, false otherwise. + */ bool isTransparent() const { return transparent; } + + /** + * @brief Sets whether the color is transparent or not. + * + * @param t True to set the color as transparent, false otherwise. + */ void setTransparent(bool t) { transparent = t; } + + /** + * @brief Sets whether the color has been modified or not. + * + * @param m True to set the color as modified, false otherwise. + */ void setModified(bool m) { modified = m; } private: - ImVec4 rgb; - uint16_t snes; - snes_color rom_color; - bool modified = false; - bool transparent = false; + ImVec4 rgb; /**< The RGB value of the color. */ + uint16_t snes; /**< The SNES value of the color. */ + snes_color + rom_color; /**< The RGB value of the color as it appears in the ROM. */ + bool modified = false; /**< Whether the color has been modified or not. */ + bool transparent = false; /**< Whether the color is transparent or not. */ }; +/** + * @brief Reads an SNES color from a ROM. + * + * @param offset The offset in the ROM to read from. + * @param rom The ROM to read from. + */ +gfx::SNESColor ReadColorFromROM(int offset, const uchar* rom); + +/** + * @brief Gets an SNES color from a CGX color. + * + * @param color The CGX color to get the SNES color from. + * @return The SNES color. + */ SNESColor GetCgxColor(uint16_t color); +/** + * @brief Gets a vector of SNES colors from a data buffer. + * + * @param data The data buffer to extract from. + * @return A vector of SNES colors extracted from the buffer. + */ std::vector GetColFileData(uchar* data); +/** + * @brief Class representing an SNES palette with additional functionality. + */ class SNESPalette { public: + /** + * @brief Constructor that takes a vector of data and initializes the palette + * with it. + * + * @tparam T The type of data in the vector. + * @param data The vector of data to initialize the palette with. + */ template explicit SNESPalette(const std::vector& data) { for (const auto& item : data) { @@ -106,16 +239,72 @@ class SNESPalette { } } + /** + * @brief Default constructor. + */ SNESPalette() = default; + + /** + * @brief Constructor that takes a size and initializes the palette with it. + * + * @param mSize The size to initialize the palette with. + */ explicit SNESPalette(uint8_t mSize); + + /** + * @brief Constructor that takes a character array and initializes the palette + * with it. + * + * @param snesPal The character array to initialize the palette with. + */ explicit SNESPalette(char* snesPal); + + /** + * @brief Constructor that takes an unsigned character array and initializes + * the palette with it. + * + * @param snes_pal The unsigned character array to initialize the palette + * with. + */ explicit SNESPalette(const unsigned char* snes_pal); + + /** + * @brief Constructor that takes a vector of ImVec4 values and initializes the + * palette with it. + * + * @param The vector of ImVec4 values to initialize the palette with. + */ explicit SNESPalette(const std::vector&); + + /** + * @brief Constructor that takes a vector of SNES colors and initializes the + * palette with it. + * + * @param The vector of SNES colors to initialize the palette with. + */ explicit SNESPalette(const std::vector&); + + /** + * @brief Constructor that takes a vector of SNES colors with additional + * functionality and initializes the palette with it. + * + * @param The vector of SNES colors with additional functionality to + * initialize the palette with. + */ explicit SNESPalette(const std::vector&); + /** + * @brief Gets the SDL palette associated with the SNES palette. + * + * @return The SDL palette. + */ SDL_Palette* GetSDL_Palette(); + /** + * @brief Creates the palette with a vector of SNES colors. + * + * @param cols The vector of SNES colors to create the palette with. + */ void Create(const std::vector& cols) { for (const auto& each : cols) { colors.push_back(each); @@ -123,16 +312,32 @@ class SNESPalette { size_ = cols.size(); } + /** + * @brief Adds an SNES color to the palette. + * + * @param color The SNES color to add. + */ void AddColor(SNESColor color) { colors.push_back(color); size_++; } + /** + * @brief Adds an SNES color to the palette. + * + * @param color The SNES color to add. + */ void AddColor(snes_color color) { colors.emplace_back(color); size_++; } + /** + * @brief Gets the color at the specified index. + * + * @param i The index of the color to get. + * @return The color at the specified index. + */ auto GetColor(int i) const { if (i > size_) { throw std::out_of_range("SNESPalette: Index out of bounds"); @@ -140,13 +345,27 @@ class SNESPalette { return colors[i]; } + /** + * @brief Clears the palette. + */ void Clear() { colors.clear(); size_ = 0; } + /** + * @brief Gets the size of the palette. + * + * @return The size of the palette. + */ auto size() const { return colors.size(); } + /** + * @brief Gets the color at the specified index. + * + * @param i The index of the color to get. + * @return The color at the specified index. + */ SNESColor operator[](int i) { if (i > size_) { throw std::out_of_range("SNESPalette: Index out of bounds"); @@ -154,6 +373,12 @@ class SNESPalette { return colors[i]; } + /** + * @brief Sets the color at the specified index. + * + * @param i The index of the color to set. + * @param color The color to set. + */ void operator()(int i, const SNESColor& color) { if (i >= size_) { throw std::out_of_range("SNESPalette: Index out of bounds"); @@ -161,6 +386,12 @@ class SNESPalette { colors[i] = color; } + /** + * @brief Sets the color at the specified index. + * + * @param i The index of the color to set. + * @param color The color to set. + */ void operator()(int i, const ImVec4& color) { if (i >= size_) { throw std::out_of_range("SNESPalette: Index out of bounds"); @@ -170,35 +401,96 @@ class SNESPalette { } private: - int size_ = 0; - std::vector colors; + int size_ = 0; /**< The size of the palette. */ + std::vector colors; /**< The colors in the palette. */ }; +SNESPalette ReadPaletteFromROM(int offset, int num_colors, const uchar* rom); + +/** + * @brief Gets the address of a palette in a group. + * + * @param group_name The name of the group. + * @param palette_index The index of the palette. + * @param color_index The index of the color. + * @return The address of the palette. + */ +uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index, + size_t color_index); + +/** + * @brief Converts an SNES color to a float array. + * + * @param color The SNES color to convert. + * @return The float array representing the SNES color. + */ std::array ToFloatArray(const SNESColor& color); +/** + * @brief Struct representing a group of SNES palettes. + */ struct PaletteGroup { + /** + * @brief Default constructor. + */ PaletteGroup() = default; + + /** + * @brief Constructor that takes a size and initializes the group with it. + * + * @param mSize The size to initialize the group with. + */ explicit PaletteGroup(uint8_t mSize); - void AddPalette(SNESPalette pal) { + /** + * @brief Adds a palette to the group. + * + * @param pal The palette to add. + * @return An absl::Status indicating whether the operation was successful or + * not. + */ + absl::Status AddPalette(SNESPalette pal) { palettes.emplace_back(pal); size_ = palettes.size(); + return absl::OkStatus(); } - void AddColor(SNESColor color) { + /** + * @brief Adds a color to the group. + * + * @param color The color to add. + * @return An absl::Status indicating whether the operation was successful or + * not. + */ + absl::Status AddColor(SNESColor color) { if (size_ == 0) { palettes.emplace_back(); } palettes[0].AddColor(color); + return absl::OkStatus(); } + /** + * @brief Clears the group. + */ void Clear() { palettes.clear(); size_ = 0; } + /** + * @brief Gets the size of the group. + * + * @return The size of the group. + */ auto size() const { return palettes.size(); } + /** + * @brief Gets the palette at the specified index. + * + * @param i The index of the palette to get. + * @return The palette at the specified index. + */ SNESPalette operator[](int i) { if (i > size_) { std::cout << "PaletteGroup: Index out of bounds" << std::endl; @@ -207,11 +499,63 @@ struct PaletteGroup { return palettes[i]; } + /** + * @brief Gets the palette at the specified index. + * + * @param i The index of the palette to get. + * @return The palette at the specified index. + */ + const SNESPalette& operator[](int i) const { + if (i > size_) { + std::cout << "PaletteGroup: Index out of bounds" << std::endl; + return palettes[0]; + } + return palettes[i]; + } + + /** + * @brief Sets the color at the specified index. + * + * @param i The index of the color to set. + * @param color The color to set. + * @return An absl::Status indicating whether the operation was successful or + * not. + */ + absl::Status operator()(int i, const SNESColor& color) { + if (i >= size_) { + return absl::InvalidArgumentError("PaletteGroup: Index out of bounds"); + } + palettes[i](0, color); + return absl::OkStatus(); + } + + /** + * @brief Sets the color at the specified index. + * + * @param i The index of the color to set. + * @param color The color to set. + * @return An absl::Status indicating whether the operation was successful or + * not. + */ + absl::Status operator()(int i, const ImVec4& color) { + if (i >= size_) { + return absl::InvalidArgumentError("PaletteGroup: Index out of bounds"); + } + palettes[i](0, color); + return absl::OkStatus(); + } + private: - int size_ = 0; - std::vector palettes; + int size_ = 0; /**< The size of the group. */ + std::vector palettes; /**< The palettes in the group. */ }; +/** + * @brief Creates a palette group from a vector of SNES colors. + * + * @param colors The vector of SNES colors to create the group from. + * @return The created palette group. + */ PaletteGroup CreatePaletteGroupFromColFile(std::vector& colors); } // namespace gfx diff --git a/src/app/gfx/snes_tile.h b/src/app/gfx/snes_tile.h index ce8f3fa7..a117a785 100644 --- a/src/app/gfx/snes_tile.h +++ b/src/app/gfx/snes_tile.h @@ -52,6 +52,13 @@ class TileInfo { vertical_mirror_(v), horizontal_mirror_(h), palette_(palette) {} + + bool operator==(const TileInfo& other) const { + return id_ == other.id_ && over_ == other.over_ && + vertical_mirror_ == other.vertical_mirror_ && + horizontal_mirror_ == other.horizontal_mirror_ && + palette_ == other.palette_; + } }; uint16_t TileInfoToWord(TileInfo tile_info); @@ -123,6 +130,13 @@ class Tile16 { tiles_info.push_back(tile2_); tiles_info.push_back(tile3_); } + + bool operator==(const Tile16& other) const { + return tile0_ == other.tile0_ && tile1_ == other.tile1_ && + tile2_ == other.tile2_ && tile3_ == other.tile3_; + } + + bool operator!=(const Tile16& other) const { return !(*this == other); } }; class OAMTile { diff --git a/src/app/rom.cc b/src/app/rom.cc index 4360e51b..d6003a11 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -28,6 +28,164 @@ namespace yaze { namespace app { +namespace { +absl::Status LoadOverworldMainPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 6; i++) { + RETURN_IF_ERROR(palette_groups["ow_main"].AddPalette( + gfx::ReadPaletteFromROM(core::overworldPaletteMain + (i * (35 * 2)), + /*num_colors*/ 35, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadOverworldAuxiliaryPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 20; i++) { + RETURN_IF_ERROR(palette_groups["ow_aux"].AddPalette(gfx::ReadPaletteFromROM( + core::overworldPaletteAuxialiary + (i * (21 * 2)), + /*num_colors*/ 21, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadOverworldAnimatedPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 14; i++) { + RETURN_IF_ERROR( + palette_groups["ow_animated"].AddPalette(gfx::ReadPaletteFromROM( + core::overworldPaletteAnimated + (i * (7 * 2)), 7, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadHUDPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 2; i++) { + RETURN_IF_ERROR(palette_groups["hud"].AddPalette( + gfx::ReadPaletteFromROM(core::hudPalettes + (i * 64), 32, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadGlobalSpritePalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + RETURN_IF_ERROR(palette_groups["global_sprites"].AddPalette( + gfx::ReadPaletteFromROM(core::globalSpritePalettesLW, 60, data))) + RETURN_IF_ERROR(palette_groups["global_sprites"].AddPalette( + gfx::ReadPaletteFromROM(core::globalSpritePalettesDW, 60, data))) + return absl::OkStatus(); +} + +absl::Status LoadArmorPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 5; i++) { + RETURN_IF_ERROR(palette_groups["armors"].AddPalette( + gfx::ReadPaletteFromROM(core::armorPalettes + (i * 30), 15, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadSwordPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 4; i++) { + RETURN_IF_ERROR(palette_groups["swords"].AddPalette( + gfx::ReadPaletteFromROM(core::swordPalettes + (i * 6), 3, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadShieldPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 3; i++) { + RETURN_IF_ERROR(palette_groups["shields"].AddPalette( + gfx::ReadPaletteFromROM(core::shieldPalettes + (i * 8), 4, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadSpriteAux1Palettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 12; i++) { + RETURN_IF_ERROR(palette_groups["sprites_aux1"].AddPalette( + gfx::ReadPaletteFromROM(core::spritePalettesAux1 + (i * 14), 7, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadSpriteAux2Palettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 11; i++) { + RETURN_IF_ERROR(palette_groups["sprites_aux2"].AddPalette( + gfx::ReadPaletteFromROM(core::spritePalettesAux2 + (i * 14), 7, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadSpriteAux3Palettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 24; i++) { + RETURN_IF_ERROR(palette_groups["sprites_aux3"].AddPalette( + gfx::ReadPaletteFromROM(core::spritePalettesAux3 + (i * 14), 7, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadDungeonMainPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 20; i++) { + RETURN_IF_ERROR( + palette_groups["dungeon_main"].AddPalette(gfx::ReadPaletteFromROM( + core::dungeonMainPalettes + (i * 180), 90, data))) + } + return absl::OkStatus(); +} + +absl::Status LoadGrassColors(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + RETURN_IF_ERROR(palette_groups["grass"].AddColor( + gfx::ReadColorFromROM(core::hardcodedGrassLW, rom_data.data()))) + RETURN_IF_ERROR(palette_groups["grass"].AddColor( + gfx::ReadColorFromROM(core::hardcodedGrassDW, rom_data.data()))) + RETURN_IF_ERROR(palette_groups["grass"].AddColor( + gfx::ReadColorFromROM(core::hardcodedGrassSpecial, rom_data.data()))); + return absl::OkStatus(); +} + +absl::Status Load3DObjectPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + RETURN_IF_ERROR(palette_groups["3d_object"].AddPalette( + gfx::ReadPaletteFromROM(core::triforcePalette, 8, data))) + RETURN_IF_ERROR(palette_groups["3d_object"].AddPalette( + gfx::ReadPaletteFromROM(core::crystalPalette, 8, data))) + return absl::OkStatus(); +} + +absl::Status LoadOverworldMiniMapPalettes(const Bytes& rom_data, + PaletteGroupMap& palette_groups) { + auto data = rom_data.data(); + for (int i = 0; i < 2; i++) { + RETURN_IF_ERROR( + palette_groups["ow_mini_map"].AddPalette(gfx::ReadPaletteFromROM( + core::overworldMiniMapPalettes + (i * 256), 128, data))) + } + return absl::OkStatus(); +} +} // namespace + absl::StatusOr ROM::Load2BppGraphics() { Bytes sheet; const uint8_t sheets[] = {113, 114, 218, 219, 220, 221}; @@ -44,8 +202,6 @@ absl::StatusOr ROM::Load2BppGraphics() { return sheet; } -// ============================================================================ - absl::Status ROM::LoadAllGraphicsData() { Bytes sheet; bool bpp3 = false; @@ -85,7 +241,24 @@ absl::Status ROM::LoadAllGraphicsData() { return absl::OkStatus(); } -// ============================================================================ +absl::Status ROM::LoadAllPalettes() { + RETURN_IF_ERROR(LoadOverworldMainPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadOverworldAuxiliaryPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadOverworldAnimatedPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadHUDPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadGlobalSpritePalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadArmorPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadSwordPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadShieldPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadSpriteAux1Palettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadSpriteAux2Palettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadSpriteAux3Palettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadDungeonMainPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadGrassColors(rom_data_, palette_groups_)); + RETURN_IF_ERROR(Load3DObjectPalettes(rom_data_, palette_groups_)); + RETURN_IF_ERROR(LoadOverworldMiniMapPalettes(rom_data_, palette_groups_)); + return absl::OkStatus(); +} absl::Status ROM::LoadFromFile(const absl::string_view& filename, bool z3_load) { @@ -141,8 +314,6 @@ absl::Status ROM::LoadFromFile(const absl::string_view& filename, return absl::OkStatus(); } -// ============================================================================ - absl::Status ROM::LoadFromPointer(uchar* data, size_t length) { if (!data) return absl::InvalidArgumentError( @@ -153,8 +324,6 @@ absl::Status ROM::LoadFromPointer(uchar* data, size_t length) { return absl::OkStatus(); } -// ============================================================================ - absl::Status ROM::LoadFromBytes(const Bytes& data) { if (data.empty()) { return absl::InvalidArgumentError( @@ -166,138 +335,6 @@ absl::Status ROM::LoadFromBytes(const Bytes& data) { return absl::OkStatus(); } -// ============================================================================ - -void ROM::LoadAllPalettes() { - // 35 colors each, 7x5 (0,2 on grid) - for (int i = 0; i < 6; i++) { - palette_groups_["ow_main"].AddPalette( - ReadPalette(core::overworldPaletteMain + (i * (35 * 2)), 35)); - } - // 21 colors each, 7x3 (8,2 and 8,5 on grid) - for (int i = 0; i < 20; i++) { - palette_groups_["ow_aux"].AddPalette( - ReadPalette(core::overworldPaletteAuxialiary + (i * (21 * 2)), 21)); - } - // 7 colors each 7x1 (0,7 on grid) - for (int i = 0; i < 14; i++) { - palette_groups_["ow_animated"].AddPalette( - ReadPalette(core::overworldPaletteAnimated + (i * (7 * 2)), 7)); - } - // 32 colors each 16x2 (0,0 on grid) - for (int i = 0; i < 2; i++) { - palette_groups_["hud"].AddPalette( - ReadPalette(core::hudPalettes + (i * 64), 32)); - } - - palette_groups_["global_sprites"].AddPalette( - ReadPalette(core::globalSpritePalettesLW, 60)); - palette_groups_["global_sprites"].AddPalette( - ReadPalette(core::globalSpritePalettesDW, 60)); - - for (int i = 0; i < 5; i++) { - palette_groups_["armors"].AddPalette( - ReadPalette(core::armorPalettes + (i * 30), 15)); - } - for (int i = 0; i < 4; i++) { - palette_groups_["swords"].AddPalette( - ReadPalette(core::swordPalettes + (i * 6), 3)); - } - for (int i = 0; i < 3; i++) { - palette_groups_["shields"].AddPalette( - ReadPalette(core::shieldPalettes + (i * 8), 4)); - } - for (int i = 0; i < 12; i++) { - palette_groups_["sprites_aux1"].AddPalette( - ReadPalette(core::spritePalettesAux1 + (i * 14), 7)); - } - for (int i = 0; i < 11; i++) { - palette_groups_["sprites_aux2"].AddPalette( - ReadPalette(core::spritePalettesAux2 + (i * 14), 7)); - } - for (int i = 0; i < 24; i++) { - palette_groups_["sprites_aux3"].AddPalette( - ReadPalette(core::spritePalettesAux3 + (i * 14), 7)); - } - for (int i = 0; i < 20; i++) { - palette_groups_["dungeon_main"].AddPalette( - ReadPalette(core::dungeonMainPalettes + (i * 180), 90)); - } - - // TODO: Make these grass colors editable color fields - palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassLW)); - palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassDW)); - palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassSpecial)); - - palette_groups_["3d_object"].AddPalette( - ReadPalette(core::triforcePalette, 8)); - palette_groups_["3d_object"].AddPalette(ReadPalette(core::crystalPalette, 8)); - - for (int i = 0; i < 2; i++) { - palette_groups_["ow_mini_map"].AddPalette( - ReadPalette(core::overworldMiniMapPalettes + (i * 256), 128)); - } -} - -// ============================================================================ - -absl::Status ROM::UpdatePaletteColor(const std::string& groupName, - size_t paletteIndex, size_t colorIndex, - const gfx::SNESColor& newColor) { - // Check if the groupName exists in the palette_groups_ map - if (palette_groups_.find(groupName) != palette_groups_.end()) { - // Check if the paletteIndex is within the range of available palettes in - // the group - if (paletteIndex < palette_groups_[groupName].size()) { - // Check if the colorIndex is within the range of available colors in the - // palette - if (colorIndex < palette_groups_[groupName][paletteIndex].size()) { - // Update the color value in the palette - palette_groups_[groupName][paletteIndex][colorIndex] = newColor; - palette_groups_[groupName][paletteIndex][colorIndex].setModified(true); - } else { - return absl::AbortedError( - "Error: Invalid color index in UpdatePaletteColor."); - } - } else { - return absl::AbortedError( - "Error: Invalid palette index in UpdatePaletteColor."); - } - } else { - return absl::AbortedError( - "Error: Invalid group name in UpdatePaletteColor"); - } - return absl::OkStatus(); -} - -// ============================================================================ - -void ROM::SavePalette(int index, const std::string& group_name, - gfx::SNESPalette& palette) { - // Iterate through all colors in the 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.isModified()) { - WriteColor(GetPaletteAddress(group_name, index, j), color); - color.setModified(false); // Reset the modified flag after saving - } - } -} - -void ROM::SaveAllPalettes() { - // Iterate through all palette_groups_ - for (auto& [group_name, palettes] : palette_groups_) { - // Iterate through all palettes in the group - for (size_t i = 0; i < palettes.size(); ++i) { - auto palette = palettes[i]; - SavePalette(i, group_name, palette); - } - } -} - -// ============================================================================ - absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { if (rom_data_.empty()) { return absl::InternalError("ROM data is empty."); @@ -338,19 +375,26 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { } // Open the file that we know exists for writing - std::fstream file(filename.data(), std::ios::binary | std::ios::out); - if (!file.is_open()) { + std::ofstream file(filename.data(), std::ios::binary); + if (!file) { return absl::InternalError( absl::StrCat("Could not open ROM file: ", filename)); } + using byte_cast = const char* (*)(const void*); + // Save the data to the file - for (auto i = 0; i < size_; ++i) { - file << rom_data_[i]; + 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.good()) { + if (!file) { return absl::InternalError( absl::StrCat("Error while writing to ROM file: ", filename)); } @@ -358,54 +402,57 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { return absl::OkStatus(); } -// ============================================================================ - -gfx::SNESColor ROM::ReadColor(int offset) { - short color = toint16(offset); - gfx::snes_color new_color; - new_color.red = (color & 0x1F) * 8; - new_color.green = ((color >> 5) & 0x1F) * 8; - new_color.blue = ((color >> 10) & 0x1F) * 8; - gfx::SNESColor snes_color(new_color); - return snes_color; -} - -// ============================================================================ - -gfx::SNESPalette ROM::ReadPalette(int offset, int num_colors) { - int color_offset = 0; - std::vector colors(num_colors); - - while (color_offset < num_colors) { - short color = toint16(offset); - gfx::snes_color new_color; - new_color.red = (color & 0x1F) * 8; - new_color.green = ((color >> 5) & 0x1F) * 8; - new_color.blue = ((color >> 10) & 0x1F) * 8; - colors[color_offset].SetSNES(gfx::ConvertRGBtoSNES(new_color)); - color_offset++; - offset += 2; +void ROM::SavePalette(int index, const std::string& group_name, + gfx::SNESPalette& palette) { + // Iterate through all colors in the 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.isModified()) { + WriteColor(gfx::GetPaletteAddress(group_name, index, j), color); + color.setModified(false); // Reset the modified flag after saving + } } - - gfx::SNESPalette palette(colors); - return palette; } -// ============================================================================ +void ROM::SaveAllPalettes() { + // Iterate through all palette_groups_ + for (auto& [group_name, palettes] : palette_groups_) { + // Iterate through all palettes in the group + for (size_t i = 0; i < palettes.size(); ++i) { + auto palette = palettes[i]; + SavePalette(i, group_name, palette); + } + } +} -uint32_t ROM::GetPaletteAddress(const std::string& groupName, - size_t paletteIndex, size_t color_index) const { - // Retrieve the base address for the palette group - uint32_t base_address = paletteGroupAddresses.at(groupName); - - // Retrieve the number of colors for each palette in the group - uint32_t colors_per_palette = paletteGroupColorCounts.at(groupName); - - // Calculate the address for the specified color in the ROM - uint32_t address = base_address + (paletteIndex * colors_per_palette * 2) + - (color_index * 2); - - return address; +absl::Status ROM::UpdatePaletteColor(const std::string& groupName, + size_t paletteIndex, size_t colorIndex, + const gfx::SNESColor& newColor) { + // Check if the groupName exists in the palette_groups_ map + if (palette_groups_.find(groupName) != palette_groups_.end()) { + // Check if the paletteIndex is within the range of available palettes in + // the group + if (paletteIndex < palette_groups_[groupName].size()) { + // Check if the colorIndex is within the range of available colors in the + // palette + if (colorIndex < palette_groups_[groupName][paletteIndex].size()) { + // Update the color value in the palette + palette_groups_[groupName][paletteIndex][colorIndex] = newColor; + palette_groups_[groupName][paletteIndex][colorIndex].setModified(true); + } else { + return absl::AbortedError( + "Error: Invalid color index in UpdatePaletteColor."); + } + } else { + return absl::AbortedError( + "Error: Invalid palette index in UpdatePaletteColor."); + } + } else { + return absl::AbortedError( + "Error: Invalid group name in UpdatePaletteColor"); + } + return absl::OkStatus(); } std::shared_ptr SharedROM::shared_rom_ = nullptr; diff --git a/src/app/rom.h b/src/app/rom.h index 47d98871..7afd6149 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -19,7 +19,8 @@ #include // for stack #include // for hash, operator== #include // for unordered_map -#include // for vector +#include +#include // for vector #include "SDL_render.h" // for SDL_Renderer #include "absl/container/flat_hash_map.h" // for flat_hash_map @@ -38,6 +39,9 @@ namespace yaze { namespace app { +using PaletteGroupMap = std::unordered_map; + +// Define an enum class for the different versions of the game enum class Z3_Version { US = 1, JP = 2, @@ -45,11 +49,11 @@ enum class Z3_Version { RANDO = 4, }; +// Define a struct to hold the version-specific constants struct VersionConstants { uint32_t kGgxAnimatedPointer; uint32_t kOverworldGfxGroups1; uint32_t kOverworldGfxGroups2; - // long ptrs all tiles of maps[high/low] (mapid* 3) uint32_t kCompressedAllMap32PointersHigh; uint32_t kCompressedAllMap32PointersLow; uint32_t overworldMapPaletteGroup; @@ -66,6 +70,7 @@ struct VersionConstants { uint32_t kSpriteBlocksetPointer; }; +// Define a map to hold the version constants for each version static const std::map kVersionConstantsMap = { {Z3_Version::US, { @@ -106,10 +111,9 @@ static const std::map kVersionConstantsMap = { 0x20000, // kMap32TileBL 0x233C0, // kMap32TileBR 0x5B97, // kSpriteBlocksetPointer - }} - -}; + }}}; +// Define some constants used throughout the ROM class constexpr uint32_t kOverworldGraphicsPos1 = 0x4F80; constexpr uint32_t kOverworldGraphicsPos2 = 0x505F; constexpr uint32_t kOverworldGraphicsPos3 = 0x513E; @@ -122,34 +126,35 @@ constexpr uint32_t kNormalGfxSpaceEnd = 0xC4200; constexpr uint32_t kLinkSpriteLocation = 0x80000; constexpr uint32_t kFontSpriteLocation = 0x70000; -const absl::flat_hash_map paletteGroupAddresses = { - {"ow_main", core::overworldPaletteMain}, - {"ow_aux", core::overworldPaletteAuxialiary}, - {"ow_animated", core::overworldPaletteAnimated}, - {"hud", core::hudPalettes}, - {"global_sprites", core::globalSpritePalettesLW}, - {"armors", core::armorPalettes}, - {"swords", core::swordPalettes}, - {"shields", core::shieldPalettes}, - {"sprites_aux1", core::spritePalettesAux1}, - {"sprites_aux2", core::spritePalettesAux2}, - {"sprites_aux3", core::spritePalettesAux3}, - {"dungeon_main", core::dungeonMainPalettes}, - {"grass", core::hardcodedGrassLW}, - {"3d_object", core::triforcePalette}, - {"ow_mini_map", core::overworldMiniMapPalettes}, -}; - -const absl::flat_hash_map paletteGroupColorCounts = { - {"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7}, - {"hud", 32}, {"global_sprites", 60}, {"armors", 15}, - {"swords", 3}, {"shields", 4}, {"sprites_aux1", 7}, - {"sprites_aux2", 7}, {"sprites_aux3", 7}, {"dungeon_main", 90}, - {"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128}, +struct WriteAction { + int address; + std::variant, gfx::SNESColor> value; }; class ROM { public: + template + absl::Status RunTransaction(Args... args) { + absl::Status status; + // Fold expression to apply the Write function on each argument + ((status = WriteHelper(args)), ...); + return status; + } + + absl::Status WriteHelper(const WriteAction& action) { + if (std::holds_alternative(action.value)) { + return Write(action.address, std::get(action.value)); + } else if (std::holds_alternative(action.value)) { + return WriteShort(action.address, std::get(action.value)); + } else if (std::holds_alternative>(action.value)) { + return WriteVector(action.address, + std::get>(action.value)); + } else if (std::holds_alternative(action.value)) { + return WriteColor(action.address, std::get(action.value)); + } + return absl::InvalidArgumentError("Invalid write argument type"); + } + /** * Loads 2bpp graphics from ROM data. * @@ -177,6 +182,17 @@ class ROM { */ absl::Status LoadAllGraphicsData(); + /** + * @brief Loads all the palettes for the game. + * + * This function loads all the palettes for the game, including overworld, + * HUD, armor, swords, shields, sprites, dungeon, grass, and 3D object + * palettes. It also adds the loaded palettes to their respective palette + * groups. + * + */ + absl::Status LoadAllPalettes(); + /** * Load ROM data from a file. * @@ -190,37 +206,84 @@ class ROM { absl::Status LoadFromBytes(const Bytes& data); /** - * @brief Loads all the palettes for the game. + * @brief Saves the ROM data to a file * - * This function loads all the palettes for the game, including overworld, - * HUD, armor, swords, shields, sprites, dungeon, grass, and 3D object - * palettes. It also adds the loaded palettes to their respective palette - * groups. + * @param backup If true, creates a backup file with timestamp in its name + * @param filename The name of the file to save the ROM data to * + * @return absl::Status Returns an OK status if the save was successful, + * otherwise returns an error status */ - void LoadAllPalettes(); + absl::Status SaveToFile(bool backup, absl::string_view filename = ""); - // Save functions + /** + * Saves the given palette to the ROM if any of its colors have been modified. + * + * @param index The index of the palette to save. + * @param group_name The name of the group containing the palette. + * @param palette The palette to save. + */ + void SavePalette(int index, const std::string& group_name, + gfx::SNESPalette& palette); + + /** + * @brief Saves all palettes in the ROM. + * + * This function iterates through all palette groups and all palettes in each + * group, and saves each palette using the SavePalette() function. + */ + void SaveAllPalettes(); + + /** + * @brief Updates a color in a specified palette group. + * + * This function updates the color at the specified `colorIndex` in the + * palette at `palette_index` within the palette group with the given + * `group_name`. If the group, palette, or color indices are invalid, an error + * is returned. + * + * @param group_name The name of the palette group to update. + * @param palette_index The index of the palette within the group to update. + * @param colorIndex The index of the color within the palette to update. + * @param newColor The new color value to set. + * + * @return An `absl::Status` indicating whether the update was successful. + * Returns `absl::OkStatus()` if successful, or an error status if the + * group, palette, or color indices are invalid. + */ absl::Status UpdatePaletteColor(const std::string& group_name, size_t palette_index, size_t colorIndex, const gfx::SNESColor& newColor); - void SavePalette(int index, const std::string& group_name, - gfx::SNESPalette& palette); - void SaveAllPalettes(); - - absl::Status SaveToFile(bool backup, absl::string_view filename = ""); // Read functions - gfx::SNESColor ReadColor(int offset); - gfx::SNESPalette ReadPalette(int offset, int num_colors); - uint8_t ReadByte(int offset) { return rom_data_[offset]; } - uint16_t ReadWord(int offset) { - return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8)); + absl::StatusOr ReadByte(int offset) { + if (offset >= rom_data_.size()) { + return absl::InvalidArgumentError("Offset out of range"); + } + return rom_data_[offset]; } - uint16_t ReadShort(int offset) { - return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8)); + + absl::StatusOr ReadWord(int offset) { + if (offset + 1 >= rom_data_.size()) { + return absl::InvalidArgumentError("Offset out of range"); + } + auto result = (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8)); + return result; } - std::vector ReadByteVector(uint32_t offset, uint32_t length) { + + absl::StatusOr ReadShort(int offset) { + if (offset + 1 >= rom_data_.size()) { + return absl::InvalidArgumentError("Offset out of range"); + } + auto result = (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8)); + return result; + } + + absl::StatusOr> ReadByteVector(uint32_t offset, + uint32_t length) { + if (offset + length > rom_data_.size()) { + return absl::InvalidArgumentError("Offset and length out of range"); + } std::vector result; for (int i = offset; i < length; i++) { result.push_back(rom_data_[i]); @@ -228,53 +291,72 @@ class ROM { return result; } - gfx::Tile16 ReadTile16(uint32_t tile16_id) { + absl::StatusOr ReadTile16(uint32_t tile16_id) { // Skip 8 bytes per tile. auto tpos = 0x78000 + (tile16_id * 0x08); gfx::Tile16 tile16; - tile16.tile0_ = gfx::WordToTileInfo(ReadShort(tpos)); + ASSIGN_OR_RETURN(auto new_tile0, ReadShort(tpos)) + tile16.tile0_ = gfx::WordToTileInfo(new_tile0); tpos += 2; - tile16.tile1_ = gfx::WordToTileInfo(ReadShort(tpos)); + ASSIGN_OR_RETURN(auto new_tile1, ReadShort(tpos)) + tile16.tile1_ = gfx::WordToTileInfo(new_tile1); tpos += 2; - tile16.tile2_ = gfx::WordToTileInfo(ReadShort(tpos)); + ASSIGN_OR_RETURN(auto new_tile2, ReadShort(tpos)) + tile16.tile2_ = gfx::WordToTileInfo(new_tile2); tpos += 2; - tile16.tile3_ = gfx::WordToTileInfo(ReadShort(tpos)); + ASSIGN_OR_RETURN(auto new_tile3, ReadShort(tpos)) + tile16.tile3_ = gfx::WordToTileInfo(new_tile3); return tile16; } - void WriteTile16(int tile16_id, const gfx::Tile16& tile) { + absl::Status WriteTile16(int tile16_id, const gfx::Tile16& tile) { // Skip 8 bytes per tile. auto tpos = 0x78000 + (tile16_id * 0x08); - WriteShort(tpos, gfx::TileInfoToWord(tile.tile0_)); + RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile0_))); tpos += 2; - WriteShort(tpos, gfx::TileInfoToWord(tile.tile1_)); + RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile1_))); tpos += 2; - WriteShort(tpos, gfx::TileInfoToWord(tile.tile2_)); + RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile2_))); tpos += 2; - WriteShort(tpos, gfx::TileInfoToWord(tile.tile3_)); + RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile3_))); + return absl::OkStatus(); } // Write functions - void Write(int addr, int value) { rom_data_[addr] = value; } - - void WriteShort(uint32_t addr, uint16_t value) { - rom_data_[addr] = (uint8_t)(value & 0xFF); - rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF); + absl::Status Write(int addr, int value) { + if (addr >= rom_data_.size()) { + return absl::InvalidArgumentError("Address out of range"); + } + rom_data_[addr] = value; + return absl::OkStatus(); } - void WriteVector(int addr, std::vector data) { + absl::Status WriteShort(uint32_t addr, uint16_t value) { + if (addr + 1 >= rom_data_.size()) { + return absl::InvalidArgumentError("Address out of range"); + } + rom_data_[addr] = (uint8_t)(value & 0xFF); + rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF); + return absl::OkStatus(); + } + + absl::Status WriteVector(int addr, std::vector data) { + if (addr + data.size() > rom_data_.size()) { + return absl::InvalidArgumentError("Address and data size out of range"); + } for (int i = 0; i < data.size(); i++) { rom_data_[addr + i] = data[i]; } + return absl::OkStatus(); } - void WriteColor(uint32_t address, const gfx::SNESColor& color) { + absl::Status WriteColor(uint32_t address, const gfx::SNESColor& color) { uint16_t bgr = ((color.GetSNES() >> 10) & 0x1F) | ((color.GetSNES() & 0x1F) << 10) | (color.GetSNES() & 0x7C00); // Write the 16-bit color value to the ROM at the specified address - WriteShort(address, bgr); + return WriteShort(address, bgr); } void Expand(int size) { @@ -298,8 +380,6 @@ class ROM { return core::SnesToPc(snes_addr); } - uint32_t GetPaletteAddress(const std::string& groupName, size_t paletteIndex, - size_t colorIndex) const; gfx::PaletteGroup GetPaletteGroup(const std::string& group) { return palette_groups_[group]; } diff --git a/src/app/zelda3/overworld.cc b/src/app/zelda3/overworld.cc index 4f3ce5a3..98bd0422 100644 --- a/src/app/zelda3/overworld.cc +++ b/src/app/zelda3/overworld.cc @@ -212,23 +212,27 @@ absl::Status Overworld::SaveOverworldMaps() { std::copy(a.begin(), a.end(), map_data_p1[i].begin()); int snes_pos = core::PcToSnes(pos); map_pointers1[i] = snes_pos; - rom()->Write(kCompressedAllMap32PointersLow + 0 + 3 * i, - static_cast(snes_pos & 0xFF)); - rom()->Write(kCompressedAllMap32PointersLow + 1 + 3 * i, - static_cast((snes_pos >> 8) & 0xFF)); - rom()->Write(kCompressedAllMap32PointersLow + 2 + 3 * i, - static_cast((snes_pos >> 16) & 0xFF)); - rom()->WriteVector(pos, a); + + RETURN_IF_ERROR(rom()->RunTransaction( + WriteAction{kCompressedAllMap32PointersLow + 0 + 3 * i, + uint8_t(snes_pos & 0xFF)}, + WriteAction{kCompressedAllMap32PointersLow + 1 + 3 * i, + uint8_t((snes_pos >> 8) & 0xFF)}, + WriteAction{kCompressedAllMap32PointersLow + 2 + 3 * i, + uint8_t((snes_pos >> 16) & 0xFF)}, + WriteAction{pos, std::vector(a)})) + pos += a.size(); } else { // Save pointer for map1 int snes_pos = map_pointers1[map_pointers1_id[i]]; - rom()->Write(kCompressedAllMap32PointersLow + 0 + 3 * i, - static_cast(snes_pos & 0xFF)); - rom()->Write(kCompressedAllMap32PointersLow + 1 + 3 * i, - static_cast((snes_pos >> 8) & 0xFF)); - rom()->Write(kCompressedAllMap32PointersLow + 2 + 3 * i, - static_cast((snes_pos >> 16) & 0xFF)); + RETURN_IF_ERROR(rom()->RunTransaction( + WriteAction{kCompressedAllMap32PointersLow + 0 + 3 * i, + uint8_t(snes_pos & 0xFF)}, + WriteAction{kCompressedAllMap32PointersLow + 1 + 3 * i, + uint8_t((snes_pos >> 8) & 0xFF)}, + WriteAction{kCompressedAllMap32PointersLow + 2 + 3 * i, + uint8_t((snes_pos >> 16) & 0xFF)})) } if (map_pointers2_id[i] == -1) { @@ -236,23 +240,25 @@ absl::Status Overworld::SaveOverworldMaps() { std::copy(b.begin(), b.end(), map_data_p2[i].begin()); int snes_pos = core::PcToSnes(pos); map_pointers2[i] = snes_pos; - rom()->Write(kCompressedAllMap32PointersHigh + 0 + 3 * i, - static_cast(snes_pos & 0xFF)); - rom()->Write(kCompressedAllMap32PointersHigh + 1 + 3 * i, - static_cast((snes_pos >> 8) & 0xFF)); - rom()->Write(kCompressedAllMap32PointersHigh + 2 + 3 * i, - static_cast((snes_pos >> 16) & 0xFF)); - rom()->WriteVector(pos, b); + RETURN_IF_ERROR(rom()->RunTransaction( + WriteAction{kCompressedAllMap32PointersHigh + 0 + 3 * i, + static_cast(snes_pos & 0xFF)}, + WriteAction{kCompressedAllMap32PointersHigh + 1 + 3 * i, + static_cast((snes_pos >> 8) & 0xFF)}, + WriteAction{kCompressedAllMap32PointersHigh + 2 + 3 * i, + static_cast((snes_pos >> 16) & 0xFF)}, + WriteAction{pos, std::vector(b)})) pos += b.size(); } else { // Save pointer for map2 int snes_pos = map_pointers2[map_pointers2_id[i]]; - rom()->Write(kCompressedAllMap32PointersHigh + 0 + 3 * i, - static_cast(snes_pos & 0xFF)); - rom()->Write(kCompressedAllMap32PointersHigh + 1 + 3 * i, - static_cast((snes_pos >> 8) & 0xFF)); - rom()->Write(kCompressedAllMap32PointersHigh + 2 + 3 * i, - static_cast((snes_pos >> 16) & 0xFF)); + RETURN_IF_ERROR(rom()->RunTransaction( + WriteAction{kCompressedAllMap32PointersHigh + 0 + 3 * i, + static_cast(snes_pos & 0xFF)}, + WriteAction{kCompressedAllMap32PointersHigh + 1 + 3 * i, + static_cast((snes_pos >> 8) & 0xFF)}, + WriteAction{kCompressedAllMap32PointersHigh + 2 + 3 * i, + static_cast((snes_pos >> 16) & 0xFF)})) } } @@ -543,18 +549,24 @@ bool Overworld::CreateTile32Tilemap(bool only_show) { // ---------------------------------------------------------------------------- -void Overworld::SaveMap16Tiles() { +absl::Status Overworld::SaveMap16Tiles() { int tpos = kMap16Tiles; // 3760 for (int i = 0; i < NumberOfMap16; i += 1) { - rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile0_)); - tpos += 2; - rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile1_)); - tpos += 2; - rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile2_)); - tpos += 2; - rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile3_)); - tpos += 2; + RETURN_IF_ERROR(rom()->RunTransaction( + WriteAction{tpos, uint16_t(TileInfoToShort(tiles16[i].tile0_))}, + WriteAction{tpos += 2, uint16_t(TileInfoToShort(tiles16[i].tile1_))}, + WriteAction{tpos += 2, uint16_t(TileInfoToShort(tiles16[i].tile2_))}, + WriteAction{tpos += 2, uint16_t(TileInfoToShort(tiles16[i].tile3_))})); + + // rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile0_)); + // tpos += 2; + // rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile1_)); + // tpos += 2; + // rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile2_)); + // tpos += 2; + // rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile3_)); + // tpos += 2; } } @@ -622,8 +634,8 @@ void Overworld::AssembleMap32Tiles() { for (int i = 0; i < 0x33F0; i += 6) { // Loop through each quadrant of the 32x32 pixel tile. for (int k = 0; k < 4; k++) { - // Generate the 16-bit tile for the current quadrant of the current 32x32 - // pixel tile. + // Generate the 16-bit tile for the current quadrant of the current + // 32x32 pixel tile. uint16_t tl = GenerateTile32(i, k, (int)Dimension::map32TilesTL); uint16_t tr = GenerateTile32(i, k, (int)Dimension::map32TilesTR); uint16_t bl = GenerateTile32(i, k, (int)Dimension::map32TilesBL); @@ -634,8 +646,8 @@ void Overworld::AssembleMap32Tiles() { } } - // Initialize the light_world, dark_world, and special_world vectors with the - // appropriate number of tiles. + // Initialize the light_world, dark_world, and special_world vectors with + // the appropriate number of tiles. map_tiles_.light_world.resize(kTile32Num); map_tiles_.dark_world.resize(kTile32Num); map_tiles_.special_world.resize(kTile32Num); diff --git a/src/app/zelda3/overworld.h b/src/app/zelda3/overworld.h index 9af3603c..1cf70eac 100644 --- a/src/app/zelda3/overworld.h +++ b/src/app/zelda3/overworld.h @@ -186,7 +186,7 @@ class Overworld : public SharedROM { absl::Status SaveLargeMaps(); bool CreateTile32Tilemap(bool onlyShow = false); - void SaveMap16Tiles(); + absl::Status SaveMap16Tiles(); absl::Status SaveMap32Tiles(); auto GetTiles16() const { return tiles16; } diff --git a/src/cli/command_handler.cc b/src/cli/command_handler.cc index f4414f62..7d5a27d0 100644 --- a/src/cli/command_handler.cc +++ b/src/cli/command_handler.cc @@ -21,20 +21,58 @@ absl::Status Tile16Transfer::handle(const std::vector& arg_vec) { ROM dest_rom; RETURN_IF_ERROR(dest_rom.LoadFromFile(arg_vec[1])) + std::vector tileIDs; + // Parse the CSV list of tile16 IDs. std::stringstream ss(arg_vec[2].data()); - for (std::string tile16_id; std::getline(ss, tile16_id, ',');) { - std::cout << "Writing tile16 ID " << tile16_id << " to dest rom." - << std::endl; + for (std::string tileID; std::getline(ss, tileID, ',');) { + if (tileID == "*") { + // for (uint32_t i = 0; i <= rom_.GetMaxTileID(); ++i) { + // tileIDs.push_back(i); + // } + break; // No need to continue parsing if * is used + } else if (tileID.find('-') != std::string::npos) { + // Handle range: split by hyphen and add all tile IDs in the range. + std::stringstream rangeSS(tileID); + std::string start; + std::string end; + std::getline(rangeSS, start, '-'); + std::getline(rangeSS, end); + uint32_t startID = std::stoi(start, nullptr, 16); + uint32_t endID = std::stoi(end, nullptr, 16); + for (uint32_t i = startID; i <= endID; ++i) { + tileIDs.push_back(i); + } + } else { + // Handle single tile ID + uint32_t tileID_int = std::stoi(tileID, nullptr, 16); + tileIDs.push_back(tileID_int); + } + } - // Convert the string to a base16 integer. - uint32_t tile16_id_int = std::stoi(tile16_id, nullptr, /*base=*/16); + for (const auto& tile16_id_int : tileIDs) { + // Compare the tile16 data between source and destination ROMs. + // auto source_tile16_data = rom_.ReadTile16(tile16_id_int); + // auto dest_tile16_data = dest_rom.ReadTile16(tile16_id_int); + ASSIGN_OR_RETURN(auto source_tile16_data, rom_.ReadTile16(tile16_id_int)) + ASSIGN_OR_RETURN(auto dest_tile16_data, dest_rom.ReadTile16(tile16_id_int)) + if (source_tile16_data != dest_tile16_data) { + // Notify user of difference + std::cout << "Difference detected in tile16 ID " << tile16_id_int + << ". Do you want to transfer it to dest rom? (y/n): "; + char userChoice; + std::cin >> userChoice; - // Read the tile16 definition from the source ROM. - auto tile16_data = rom_.ReadTile16(tile16_id_int); - - // Write the tile16 definition to the destination ROM. - dest_rom.WriteTile16(tile16_id_int, tile16_data); + // Transfer if user confirms + if (userChoice == 'y' || userChoice == 'Y') { + dest_rom.WriteTile16(tile16_id_int, source_tile16_data); + std::cout << "Transferred tile16 ID " << tile16_id_int + << " to dest rom." << std::endl; + } else { + std::cout << "Skipped transferring tile16 ID " << tile16_id_int << "." + << std::endl; + } + } } RETURN_IF_ERROR(dest_rom.SaveToFile(/*backup=*/true, arg_vec[1])) diff --git a/src/cli/command_handler.h b/src/cli/command_handler.h index 831ef77a..5b40cad2 100644 --- a/src/cli/command_handler.h +++ b/src/cli/command_handler.h @@ -230,14 +230,18 @@ class ReadFromRom : public CommandHandler { } if (length > 1) { - auto returned_bytes = rom_.ReadByteVector(offset, length); + auto returned_bytes_status = rom_.ReadByteVector(offset, length); + if (!returned_bytes_status.ok()) { + return returned_bytes_status.status(); + } + auto returned_bytes = returned_bytes_status.value(); for (const auto& each : returned_bytes) { std::cout << each; } std::cout << std::endl; } else { auto byte = rom_.ReadByte(offset); - std::cout << std::hex << byte << std::endl; + std::cout << std::hex << byte.value() << std::endl; } return absl::OkStatus();