From 69e9ecd35c6a198ca3716aef7b61d8b16521605e Mon Sep 17 00:00:00 2001 From: modeco80 Date: Thu, 16 Jan 2025 16:24:12 -0500 Subject: [PATCH] libeuropa/io/yatd: Support YATF v2 (Jedi Starfighter) Rather confusingly, it seems like they wanted to make this backwards compatible. In practice, due to the magic fourcc being written with a different endian, it's not really. (OK well the header is the same but you get my point) Also, for some reason, they decided to I guess jumble format types around. Thanks guys. --- include/europa/structs/Yatf.hpp | 23 +++-- src/libeuropa/io/yatf/Reader.cpp | 149 ++++++++++++++++++++++++------- src/tools/texdump.cpp | 3 + 3 files changed, 136 insertions(+), 39 deletions(-) diff --git a/include/europa/structs/Yatf.hpp b/include/europa/structs/Yatf.hpp index 574a9a1..ae4492d 100644 --- a/include/europa/structs/Yatf.hpp +++ b/include/europa/structs/Yatf.hpp @@ -17,21 +17,28 @@ namespace europa::structs { struct [[gnu::packed]] YatfHeader { enum class TextureFormat : u8 { - kTextureFormat8Bpp = 0, - kTextureFormatUnknown = 1, // possibly 16bpp? - kTextureFormat24Bpp = 2, - kTextureFormat32Bpp = 3 + kTextureFormatV1_8Bpp = 0, + kTextureFormatV1_24Bpp = 2, + kTextureFormatV1_32Bpp = 3, + + // V2/jsf uses these + kTextureFormatV2_8Bpp = 1, + kTextureFormatV2_24Bpp = 3, + kTextureFormatV2_32Bpp = 4, + kTextureFormatV2_4Bpp = 5 }; - constexpr static auto ValidMagic = util::FourCC<"YATF", std::endian::big>(); + // For some reason Jedi Starfighter YATFs use a different fourcc. ??? + constexpr static auto ValidMagicSF = util::FourCC<"YATF", std::endian::big>(); + constexpr static auto ValidMagicJSF = util::FourCC<"YATF", std::endian::little>(); u32 magic; - u16 unkThing; // always 0x1 + u16 version; // 0x1 for starfighter, 0x2 for jsf TextureFormat format; - u8 unkThing2; // flags? + u8 unkThing2; // flags? some palbpp? // Always zeroed. u32 zero; @@ -40,7 +47,7 @@ namespace europa::structs { u32 width; [[nodiscard]] constexpr bool IsValid() const { - return magic == ValidMagic; + return magic == ValidMagicSF || magic == ValidMagicJSF; } }; diff --git a/src/libeuropa/io/yatf/Reader.cpp b/src/libeuropa/io/yatf/Reader.cpp index 1669337..24678c2 100644 --- a/src/libeuropa/io/yatf/Reader.cpp +++ b/src/libeuropa/io/yatf/Reader.cpp @@ -31,48 +31,135 @@ namespace europa::io::yatf { surface.Resize(imageSize); - using enum structs::YatfHeader::TextureFormat; - switch(header.format) { - case kTextureFormat8Bpp: { - util::Pixel palette[256] {}; - util::UniqueArray palettizedData(imageSize.Linear()); + // FIXME: merge this code and make it better + // i.e: let's share de-palettization between the two. + // (or upgrade ImageSurface to support multiple formats, and allow conversion to a new one? + // that might preclude cleaning it up and making util::image namespace however) - stream.read(reinterpret_cast(&palette[0]), sizeof(palette)); - stream.read(reinterpret_cast(&palettizedData[0]), imageSize.Linear()); + auto v1 = [&]() { + using enum structs::YatfHeader::TextureFormat; - auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + switch(header.format) { + case kTextureFormatV1_8Bpp: { + util::Pixel palette[256] {}; + util::UniqueArray palettizedData(imageSize.Linear()); - for(std::size_t y = 0; y < imageSize.height; ++y) { - for(std::size_t x = 0; x < imageSize.width; ++x) { - auto& pp = palettizedData[y * imageSize.width + x]; - auto& dst = pDestBuffer[y * imageSize.width + x]; - dst = palette[static_cast(pp)]; + stream.read(reinterpret_cast(&palette[0]), sizeof(palette)); + stream.read(reinterpret_cast(&palettizedData[0]), imageSize.Linear()); + + auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + + for(std::size_t y = 0; y < imageSize.height; ++y) { + for(std::size_t x = 0; x < imageSize.width; ++x) { + auto& pp = palettizedData[y * imageSize.width + x]; + auto& dst = pDestBuffer[y * imageSize.width + x]; + dst = palette[static_cast(pp)]; + } } - } - } break; + } break; - case kTextureFormat24Bpp: { - util::UniqueArray rgbPixelData(imageSize.Linear()); - stream.read(reinterpret_cast(&rgbPixelData[0]), imageSize.LinearWithStride()); - auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + case kTextureFormatV1_24Bpp: { + util::UniqueArray rgbPixelData(imageSize.Linear()); + stream.read(reinterpret_cast(&rgbPixelData[0]), imageSize.LinearWithStride()); + auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); - for(std::size_t y = 0; y < imageSize.height; ++y) { - for(std::size_t x = 0; x < imageSize.width; ++x) { - auto& pp = rgbPixelData[y * imageSize.width + x]; - auto& dst = pDestBuffer[y * imageSize.width + x]; - dst = util::Pixel::FromPixelRGB(pp); + for(std::size_t y = 0; y < imageSize.height; ++y) { + for(std::size_t x = 0; x < imageSize.width; ++x) { + auto& pp = rgbPixelData[y * imageSize.width + x]; + auto& dst = pDestBuffer[y * imageSize.width + x]; + dst = util::Pixel::FromPixelRGB(pp); + } } - } - } break; + } break; - case kTextureFormat32Bpp: - // We can directly read data - stream.read(reinterpret_cast(surface.GetBuffer()), imageSize.LinearWithStride()); + case kTextureFormatV1_32Bpp: + // We can directly read data + stream.read(reinterpret_cast(surface.GetBuffer()), imageSize.LinearWithStride()); + break; + + default: + throw std::runtime_error(std::format("Unknown/unsupported texture format {:02x}!", (std::uint16_t)header.format)); + break; + } + }; + + auto v2 = [&]() { + using enum structs::YatfHeader::TextureFormat; + + switch(header.format) { + case kTextureFormatV2_4Bpp: { + util::Pixel palette[16] {}; + util::UniqueArray palettizedData(imageSize.Linear() / 2); + + stream.read(reinterpret_cast(&palette[0]), sizeof(palette)); + stream.read(reinterpret_cast(&palettizedData[0]), imageSize.Linear() / 2); + + auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + + // can't really get a better loop to work, so i guess this has to do + for(std::size_t y = 0; y < (imageSize.width * imageSize.height) / 2; ++y) { + auto& pp = palettizedData[y]; + for(std::size_t b = 0; b < 2; ++b) { + auto col = ((pp & (0x0F << (b * 4))) >> (b * 4)); + (*pDestBuffer++) = palette[static_cast(col)]; + } + } + } break; + + case kTextureFormatV2_8Bpp: { + util::Pixel palette[256] {}; + util::UniqueArray palettizedData(imageSize.Linear()); + + stream.read(reinterpret_cast(&palette[0]), sizeof(palette)); + stream.read(reinterpret_cast(&palettizedData[0]), imageSize.Linear()); + + auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + + for(std::size_t y = 0; y < imageSize.height; ++y) { + for(std::size_t x = 0; x < imageSize.width; ++x) { + auto& pp = palettizedData[y * imageSize.width + x]; + auto& dst = pDestBuffer[y * imageSize.width + x]; + dst = palette[static_cast(pp)]; + } + } + } break; + + case kTextureFormatV2_24Bpp: { + util::UniqueArray rgbPixelData(imageSize.Linear()); + stream.read(reinterpret_cast(&rgbPixelData[0]), imageSize.LinearWithStride()); + auto* pDestBuffer = reinterpret_cast(surface.GetBuffer()); + + for(std::size_t y = 0; y < imageSize.height; ++y) { + for(std::size_t x = 0; x < imageSize.width; ++x) { + auto& pp = rgbPixelData[y * imageSize.width + x]; + auto& dst = pDestBuffer[y * imageSize.width + x]; + dst = util::Pixel::FromPixelRGB(pp); + } + } + } break; + + case kTextureFormatV2_32Bpp: + // We can directly read data + stream.read(reinterpret_cast(surface.GetBuffer()), imageSize.LinearWithStride()); + break; + + default: + throw std::runtime_error(std::format("Unknown/unsupported texture format {:02x}!", (std::uint16_t)header.format)); + break; + } + }; + + switch(header.version) { + case 1: + v1(); + break; + + case 2: + v2(); break; - case kTextureFormatUnknown: default: - throw std::runtime_error(std::format("Unknown/unsupported texture format {:02x}!", (std::uint16_t)header.format)); + throw std::runtime_error(std::format("Unknown/unsupported YATF version {:02x}!", (std::uint16_t)header.version)); break; } diff --git a/src/tools/texdump.cpp b/src/tools/texdump.cpp index a923b9e..243eada 100644 --- a/src/tools/texdump.cpp +++ b/src/tools/texdump.cpp @@ -40,6 +40,9 @@ int main(int argc, char** argv) { europa::structs::YatfHeader yatfHeader; eutil::ImageSurface surface; + + std::cout << "Opening \"" << argv[1] << "\"\n"; + if(!reader.ReadImage(yatfHeader, surface)) { std::cout << "Invalid YATF file \"" << argv[1] << "\"\n"; return 1;