diff --git a/source/graphics.cpp b/source/graphics.cpp index 4bca416e..425be4fb 100644 --- a/source/graphics.cpp +++ b/source/graphics.cpp @@ -1136,6 +1136,15 @@ std::shared_ptr GameSprite::getOutfitImage(int spriteId wxMemoryDC* GameSprite::getDC(SpriteSize spriteSize) { ASSERT(spriteSize == SPRITE_SIZE_16x16 || spriteSize == SPRITE_SIZE_32x32); + if (!width && !height) { + // Initialize default draw offset + const auto &sheet = g_spriteAppearances.getSheetBySpriteId(spriteList[0]->getHardwareID(), false); + if (sheet) { + width = sheet->getSpriteSize().width; + height = sheet->getSpriteSize().height; + } + } + if (!m_wxMemoryDc[spriteSize]) { ASSERT(width >= 1 && height >= 1); @@ -1274,7 +1283,7 @@ void GameSprite::NormalImage::clean(int time) { uint8_t* GameSprite::NormalImage::getRGBAData() { if (!m_cachedData) { if (!g_gui.gfx.loadSpriteDump(m_cachedData, size, id)) { - spdlog::info("[GameSprite::NormalImage::getRGBAData] - Failed when parsing sprite id {}", id); + spdlog::error("[GameSprite::NormalImage::getRGBAData] - Failed when parsing sprite id {}", id); return nullptr; } } diff --git a/source/sprite_appearances.cpp b/source/sprite_appearances.cpp index 269d85cd..4303395e 100644 --- a/source/sprite_appearances.cpp +++ b/source/sprite_appearances.cpp @@ -24,12 +24,6 @@ #include -// APPEARANCES -#define BYTES_IN_SPRITE_SHEET 384 * 384 * 4 -#define LZMA_UNCOMPRESSED_SIZE BYTES_IN_SPRITE_SHEET + 122 -#define LZMA_HEADER_SIZE LZMA_PROPS_SIZE + 8 -#define SPRITE_SHEET_WIDTH_BYTES 384 * 4 - namespace fs = std::filesystem; SpriteAppearances g_spriteAppearances; @@ -169,13 +163,12 @@ bool SpriteAppearances::loadSpriteSheet(const SpriteSheetPtr &sheet) { uint8_t* bufferStart = decompressed.get() + data; // Flip vertically - for (int y = 0; y < 192; ++y) { + // Flip vertically + for (int y = 0; y < SPRITE_SHEET_HEIGHT / 2; ++y) { uint8_t* itr1 = &bufferStart[y * SPRITE_SHEET_WIDTH_BYTES]; - uint8_t* itr2 = &bufferStart[(384 - y - 1) * SPRITE_SHEET_WIDTH_BYTES]; + uint8_t* itr2 = &bufferStart[(SPRITE_SHEET_WIDTH - y - 1) * SPRITE_SHEET_WIDTH_BYTES]; - for (std::size_t x = 0; x < SPRITE_SHEET_WIDTH_BYTES; ++x) { - std::swap(*(itr1 + x), *(itr2 + x)); - } + std::swap_ranges(itr1, itr1 + SPRITE_SHEET_WIDTH_BYTES, itr2); } sheet->data = std::make_unique(LZMA_UNCOMPRESSED_SIZE); @@ -265,35 +258,93 @@ wxImage SpriteAppearances::getWxImageBySpriteId(int id, bool toSavePng /* = fals } SpritePtr SpriteAppearances::getSprite(int spriteId) { - // caching + // Caching auto it = sprites.find(spriteId); if (it != sprites.end()) { + spdlog::debug("Sprite {} found in cache.", spriteId); return it->second; } - const auto &sheet = getSheetBySpriteId(spriteId); + // Retrieve sprite sheet + const auto& sheet = getSheetBySpriteId(spriteId); if (!sheet || !sheet->loaded) { + spdlog::warn("Sprite sheet for sprite {} is not loaded or null.", spriteId); return nullptr; } + // Get sprite dimensions auto spriteWidth = sheet->getSpriteSize().width; auto spriteHeight = sheet->getSpriteSize().height; + // Validate dimensions + if (spriteWidth <= 0 || spriteHeight <= 0) { + spdlog::error("Invalid sprite dimensions: width = {}, height = {}", spriteWidth, spriteHeight); + return nullptr; + } + + // Allocate sprite SpritePtr sprite = SpritePtr(new Sprites(spriteWidth, spriteHeight)); + // Calculate sprite offset int spriteOffset = spriteId - sheet->firstId; - int allColumns = spriteWidth == 32 ? 12 : 6; // 64 pixel width == 6 columns each 64x or 32 pixels, 12 columns - int spriteRow = static_cast(std::floor(static_cast(spriteOffset) / static_cast(allColumns))); + int allColumns = (spriteWidth == 32) ? 12 : 6; + + // Validate sprite offset + int totalNumberOfSpritesInSheet = sheet->getTotalSprites(); + if (spriteOffset < 0 || spriteOffset >= totalNumberOfSpritesInSheet) { + spdlog::warn("Sprite offset out of range: offset = {}, total sprites = {}", spriteOffset, totalNumberOfSpritesInSheet); + return nullptr; + } + + // Calculate row and column + int spriteRow = spriteOffset / allColumns; int spriteColumn = spriteOffset % allColumns; + // Validate row and column + int totalRows = sheet->getTotalRows(); + if (spriteRow < 0 || spriteRow >= totalRows || spriteColumn < 0 || spriteColumn >= allColumns) { + spdlog::warn("Invalid sprite row/column: row = {}, column = {}, total rows = {}, allColumns = {}", spriteRow, spriteColumn, totalRows, allColumns); + return nullptr; + } + + // Validate memory for sheet->data + if (!sheet->data) { + spdlog::error("Sheet data is null for sprite {}.", spriteId); + return nullptr; + } + + // Update bufferSize based on actual sheet height + size_t bufferSize = SPRITE_SHEET_HEIGHT * SPRITE_SHEET_WIDTH_BYTES; + + // Validate pixel buffer size + size_t pixelBufferSize = sprite->pixels.size(); + if (pixelBufferSize < static_cast(spriteWidth * spriteHeight * 4)) { + spdlog::error("Insufficient pixel buffer size for sprite {}: pixelBufferSize = {}, expected = {}", + spriteId, pixelBufferSize, spriteWidth * spriteHeight * 4); + return nullptr; + } + + // Adjust loop to prevent height from exceeding SPRITE_SHEET_HEIGHT + int maxHeight = std::min((spriteRow + 1) * spriteHeight, SPRITE_SHEET_HEIGHT); int spriteWidthBytes = spriteWidth * 4; + for (int height = spriteHeight * spriteRow, offset = 0; height < maxHeight; height++, offset++) { + size_t bufferDataStart = (height * SPRITE_SHEET_WIDTH_BYTES) + (spriteColumn * spriteWidthBytes); + + // Validate access + if (bufferDataStart + spriteWidthBytes > bufferSize) { + spdlog::error("Out-of-bounds access during copy: spriteId = {}, height = {}, offset = {}, bufferDataStart = {}, bufferSize = {}", + spriteId, height, offset, bufferDataStart, bufferSize); + return nullptr; + } + + auto bufferData = &sheet->data[bufferDataStart]; + auto dest = &sprite->pixels[offset * spriteWidthBytes]; - for (int height = spriteHeight * spriteRow, offset = 0; height < spriteHeight + (spriteRow * spriteHeight); height++, offset++) { - auto bufferData = &sheet->data[(height * SPRITE_SHEET_WIDTH_BYTES) + (spriteColumn * spriteWidthBytes)]; - std::memcpy(&sprite->pixels[offset * spriteWidthBytes], bufferData, spriteWidthBytes); + // Copy data using std::ranges::copy + std::ranges::copy(std::span(bufferData, spriteWidthBytes), dest); } - // cache it for faster later access + // Cache the sprite sprites[spriteId] = sprite; return sprite; diff --git a/source/sprite_appearances.h b/source/sprite_appearances.h index 93bf82c8..ff0c4e8c 100644 --- a/source/sprite_appearances.h +++ b/source/sprite_appearances.h @@ -24,6 +24,15 @@ class GameSprite; +// APPEARANCES +#define LZMA_UNCOMPRESSED_SIZE BYTES_IN_SPRITE_SHEET + 122 +#define LZMA_HEADER_SIZE LZMA_PROPS_SIZE + 8 + +#define SPRITE_SHEET_WIDTH 384 +#define SPRITE_SHEET_HEIGHT 384 +#define BYTES_IN_SPRITE_SHEET SPRITE_SHEET_WIDTH * SPRITE_SHEET_WIDTH * 4 +#define SPRITE_SHEET_WIDTH_BYTES SPRITE_SHEET_WIDTH * 4 + enum class SpriteLayout { ONE_BY_ONE = 0, ONE_BY_TWO = 1, @@ -86,7 +95,7 @@ class SpriteSheet { SpriteSheet(int firstId, int lastId, SpriteLayout spriteLayout, const std::string &path) : firstId(firstId), lastId(lastId), spriteLayout(spriteLayout), path(path) { } - SpritesSize getSpriteSize() { + SpritesSize getSpriteSize() const { SpritesSize size(rme::SpritePixels, rme::SpritePixels); switch (spriteLayout) { @@ -107,6 +116,25 @@ class SpriteSheet { return size; } + int getTotalSprites() const { + return lastId - firstId + 1; + } + + int getTotalRows() const { + auto spriteSize = getSpriteSize(); + if (spriteSize.width == 0) { + return 0; // Avoid division by zero + } + + int spritesPerRow = SPRITE_SHEET_WIDTH / spriteSize.width; // Use the actual sheet width + if (spritesPerRow == 0) { + return 0; // No rows if sprites don't fit in a line + } + + // Calculate total sprites and round up division for rows + return (getTotalSprites() + spritesPerRow - 1) / spritesPerRow; + } + bool exportSheetImage(const std::string &file, bool fixMagenta = false) { wxImage image(384, 384, data.get(), true); return image.SaveFile(wxString(file), wxBITMAP_TYPE_PNG);