Skip to content

Commit

Permalink
fix: crash related to sprite width/height and std::memcpy
Browse files Browse the repository at this point in the history
  • Loading branch information
dudantas committed Nov 22, 2024
1 parent 49814b1 commit 732d19d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 21 deletions.
11 changes: 10 additions & 1 deletion source/graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,15 @@ std::shared_ptr<GameSprite::OutfitImage> 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);

Expand Down Expand Up @@ -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;
}
}
Expand Down
89 changes: 70 additions & 19 deletions source/sprite_appearances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@

#include <lzma.h>

// 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;
Expand Down Expand Up @@ -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<uint8_t[]>(LZMA_UNCOMPRESSED_SIZE);
Expand Down Expand Up @@ -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<int>(std::floor(static_cast<float>(spriteOffset) / static_cast<float>(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<size_t>(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;
Expand Down
30 changes: 29 additions & 1 deletion source/sprite_appearances.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down

0 comments on commit 732d19d

Please sign in to comment.