#include "levels/reencode_dun_cels.hpp" #include #include #include #include #include #include #include #include "levels/dun_tile.hpp" #include "utils/attributes.h" #include "utils/endian.hpp" #include "utils/format_int.hpp" #include "utils/log.hpp" namespace devilution { namespace { DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTriangleLower(uint8_t *&dst, const uint8_t *&src) { unsigned width = 0; for (unsigned i = 0; i < 8; ++i) { src += 2; // Skips the two zero bytes (aka bloat). width += 2; std::memcpy(dst, src, width); src += width; dst += width; width += 2; std::memcpy(dst, src, width); src += width; dst += width; } } DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTriangle(uint8_t *&dst, const uint8_t *&src) { ReencodeDungeonCelsLeftTriangleLower(dst, src); unsigned width = DunFrameWidth; for (unsigned i = 0; i < 7; ++i) { src += 2; // Skips the two zero bytes (aka bloat). width -= 2; std::memcpy(dst, src, width); src += width; dst += width; width -= 2; std::memcpy(dst, src, width); src += width; dst += width; } src += 2; // Skips the two zero bytes (aka bloat). width -= 2; std::memcpy(dst, src, width); dst += width; } DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTriangleLower(uint8_t *&dst, const uint8_t *&src) { unsigned width = 0; for (unsigned i = 0; i < 8; ++i) { width += 2; std::memcpy(dst, src, width); src += width + 2; // Skips the two zero bytes (aka bloat). dst += width; width += 2; std::memcpy(dst, src, width); src += width; dst += width; } } DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTriangle(uint8_t *&dst, const uint8_t *&src) { ReencodeDungeonCelsRightTriangleLower(dst, src); unsigned width = DunFrameWidth; for (unsigned i = 0; i < 7; ++i) { width -= 2; std::memcpy(dst, src, width); src += width + 2; // Skips the two zero bytes (aka bloat). dst += width; width -= 2; std::memcpy(dst, src, width); src += width; dst += width; } width -= 2; std::memcpy(dst, src, width); dst += width; } DVL_ALWAYS_INLINE void ReencodeDungeonCelsLeftTrapezoid(uint8_t *&dst, const uint8_t *&src) { ReencodeDungeonCelsLeftTriangleLower(dst, src); std::memcpy(dst, src, DunFrameWidth * 16); dst += DunFrameWidth * 16; } DVL_ALWAYS_INLINE void ReencodeDungeonCelsRightTrapezoid(uint8_t *&dst, const uint8_t *&src) { ReencodeDungeonCelsRightTriangleLower(dst, src); std::memcpy(dst, src, DunFrameWidth * 16); dst += DunFrameWidth * 16; } DVL_ALWAYS_INLINE void RenderTransparentSquare(uint8_t *dst, const uint8_t *src) { for (unsigned i = 0; i < DunFrameHeight; ++i, dst -= 2 * DunFrameWidth) { uint_fast8_t drawWidth = DunFrameWidth; while (drawWidth > 0) { auto v = static_cast(*src++); if (v > 0) { std::memcpy(dst, src, v); src += v; } else { v = static_cast(-v); } dst += v; drawWidth -= v; } } } DVL_ALWAYS_INLINE void ExtractFoliageLeftTriangle(uint8_t *&dst, uint8_t *src) { for (int w = 2, y = 31; y >= 16; --y, w += 2, src -= DunFrameWidth) { std::memcpy(dst, src + (DunFrameWidth - w), w); std::memset(src + (DunFrameWidth - w), 0, w); dst += w; } for (int w = 30, y = 15; y > 0; --y, w -= 2, src -= DunFrameWidth) { std::memcpy(dst, src + (DunFrameWidth - w), w); std::memset(src + (DunFrameWidth - w), 0, w); dst += w; } } DVL_ALWAYS_INLINE void ExtractFoliageRightTriangle(uint8_t *&dst, uint8_t *src) { for (int w = 2, y = 31; y >= 16; --y, w += 2, src -= DunFrameWidth) { std::memcpy(dst, src, w); std::memset(src, 0, w); dst += w; } for (int w = 30, y = 15; y > 0; --y, w -= 2, src -= DunFrameWidth) { std::memcpy(dst, src, w); std::memset(src, 0, w); dst += w; } } DVL_ALWAYS_INLINE void ExtractFoliageTransparentSquare(uint8_t *&dst, const uint8_t *src) { // The bottom 16 lines are always transparent, foliage only // applies to the upper half of the tile. src -= DunFrameHeight * 16; for (int y = 16; y > 0; --y, src -= 2 * DunFrameWidth) { unsigned transparentRun = 0; unsigned solidRun = 0; for (int x = 0; x < DunFrameWidth; ++x) { if (*src++ != 0) { if (transparentRun != 0) { *dst++ = static_cast(-static_cast(transparentRun)); transparentRun = 0; } ++solidRun; } else { if (solidRun != 0) { *dst++ = solidRun; std::memcpy(dst, src - solidRun, solidRun); dst += solidRun; solidRun = 0; } ++transparentRun; } } if (transparentRun != 0) { *dst++ = static_cast(-static_cast(transparentRun)); } else if (solidRun != 0) { *dst++ = solidRun; std::memcpy(dst, src - solidRun, solidRun); dst += solidRun; } } } DVL_ALWAYS_INLINE void ReencodeFloorWithFoliage(uint8_t *&dst, const uint8_t *&src, TileType tileType) { uint8_t surface[DunFrameWidth * DunFrameHeight] {}; uint8_t *surfaceLastLine = &surface[DunFrameWidth * (DunFrameHeight - 1)]; RenderTransparentSquare(surfaceLastLine, src); if (tileType == TileType::LeftTriangle) { ExtractFoliageLeftTriangle(dst, surfaceLastLine); } else { ExtractFoliageRightTriangle(dst, surfaceLastLine); } ExtractFoliageTransparentSquare(dst, surfaceLastLine); } size_t GetReencodedSize(const uint8_t *dungeonCels, std::span> frames) { size_t result = (2 + frames.size()) * 4; const auto *srcOffsets = reinterpret_cast(dungeonCels); for (const auto &[frame, info] : frames) { size_t frameSize; switch (info.type) { case TileType::TransparentSquare: { const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); if (info.isFloor()) { uint8_t out[1024]; uint8_t *outIt = out; const uint8_t *src = &dungeonCels[srcFrameBegin]; const TileType newType = info.isFloorLeft() ? TileType::LeftTriangle : TileType::RightTriangle; ReencodeFloorWithFoliage(outIt, src, newType); frameSize = outIt - out; } else { const uint32_t srcFrameEnd = SDL_SwapLE32(srcOffsets[frame + 1]); frameSize = srcFrameEnd - srcFrameBegin; } } break; case TileType::Square: { frameSize = DunFrameWidth * DunFrameHeight; } break; case TileType::LeftTriangle: case TileType::RightTriangle: frameSize = ReencodedTriangleFrameSize; break; case TileType::LeftTrapezoid: case TileType::RightTrapezoid: frameSize = ReencodedTrapezoidFrameSize; break; } result += frameSize; } return result; } } // namespace void ReencodeDungeonCels(std::unique_ptr &dungeonCels, std::span> frames) { const auto *srcData = reinterpret_cast(dungeonCels.get()); const auto *srcOffsets = reinterpret_cast(srcData); int numFoliage = 0; LogVerbose("Re-encoding dungeon CELs: {} frames, {} bytes", FormatInteger(SDL_SwapLE32(srcOffsets[0])), FormatInteger(SDL_SwapLE32(srcOffsets[SDL_SwapLE32(srcOffsets[0]) + 1]))); const size_t outSize = GetReencodedSize(srcData, frames); std::unique_ptr result { new std::byte[outSize] }; auto *const resultPtr = reinterpret_cast(result.get()); WriteLE32(resultPtr, static_cast(frames.size())); uint8_t *out = resultPtr + (2 + frames.size()) * 4; // number of frames, frame offsets, file size for (const auto &[frame, info] : frames) { WriteLE32(&resultPtr[static_cast(frame * 4)], static_cast(out - resultPtr)); const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); const uint8_t *src = &srcData[srcFrameBegin]; switch (info.type) { case TileType::TransparentSquare: { if (info.isFloor()) { const TileType newType = info.isFloorLeft() ? TileType::LeftTriangle : TileType::RightTriangle; ReencodeFloorWithFoliage(out, src, newType); ++numFoliage; } else { const uint32_t srcFrameEnd = SDL_SwapLE32(srcOffsets[frame + 1]); const uint32_t size = srcFrameEnd - srcFrameBegin; std::memcpy(out, src, size); out += size; } } break; case TileType::Square: std::memcpy(out, src, DunFrameWidth * DunFrameHeight); out += DunFrameWidth * DunFrameHeight; break; case TileType::LeftTriangle: ReencodeDungeonCelsLeftTriangle(out, src); break; case TileType::RightTriangle: ReencodeDungeonCelsRightTriangle(out, src); break; case TileType::LeftTrapezoid: ReencodeDungeonCelsLeftTrapezoid(out, src); break; case TileType::RightTrapezoid: ReencodeDungeonCelsRightTrapezoid(out, src); break; } } WriteLE32(&resultPtr[(1 + frames.size()) * 4], static_cast(outSize)); const auto *dstOffsets = reinterpret_cast(resultPtr); LogVerbose(" Re-encoded dungeon CELs: {} frames, {} bytes. Extracted {} foliage tiles.", FormatInteger(SDL_SwapLE32(dstOffsets[0])), FormatInteger(SDL_SwapLE32(dstOffsets[SDL_SwapLE32(dstOffsets[0]) + 1])), FormatInteger(numFoliage)); dungeonCels = std::move(result); } } // namespace devilution