#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 { constexpr size_t LowerTriangleBloat = 16; constexpr size_t TriangleBloat = 32; 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; } 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, type] : frames) { size_t frameSize; switch (type) { case TileType::TransparentSquare: { const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); 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 = 544 - TriangleBloat; break; case TileType::LeftTrapezoid: case TileType::RightTrapezoid: frameSize = 800 - LowerTriangleBloat; 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); 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, frames.size()); uint8_t *out = resultPtr + (2 + frames.size()) * 4; // number of frames, frame offsets, file size for (const auto &[frame, type] : frames) { WriteLE32(&resultPtr[static_cast(frame * 4)], out - resultPtr); const uint32_t srcFrameBegin = SDL_SwapLE32(srcOffsets[frame]); const uint8_t *src = &srcData[srcFrameBegin]; switch (type) { case TileType::TransparentSquare: { 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], outSize); const auto *dstOffsets = reinterpret_cast(resultPtr); LogVerbose(" Re-encoded dungeon CELs: {} frames, {} bytes", FormatInteger(SDL_SwapLE32(dstOffsets[0])), FormatInteger(SDL_SwapLE32(dstOffsets[SDL_SwapLE32(dstOffsets[0]) + 1]))); dungeonCels = std::move(result); } } // namespace devilution