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.
This commit is contained in:
parent
ab496ed4aa
commit
69e9ecd35c
3 changed files with 136 additions and 39 deletions
|
@ -17,21 +17,28 @@ namespace europa::structs {
|
||||||
|
|
||||||
struct [[gnu::packed]] YatfHeader {
|
struct [[gnu::packed]] YatfHeader {
|
||||||
enum class TextureFormat : u8 {
|
enum class TextureFormat : u8 {
|
||||||
kTextureFormat8Bpp = 0,
|
kTextureFormatV1_8Bpp = 0,
|
||||||
kTextureFormatUnknown = 1, // possibly 16bpp?
|
kTextureFormatV1_24Bpp = 2,
|
||||||
kTextureFormat24Bpp = 2,
|
kTextureFormatV1_32Bpp = 3,
|
||||||
kTextureFormat32Bpp = 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;
|
u32 magic;
|
||||||
|
|
||||||
u16 unkThing; // always 0x1
|
u16 version; // 0x1 for starfighter, 0x2 for jsf
|
||||||
|
|
||||||
TextureFormat format;
|
TextureFormat format;
|
||||||
|
|
||||||
u8 unkThing2; // flags?
|
u8 unkThing2; // flags? some palbpp?
|
||||||
|
|
||||||
// Always zeroed.
|
// Always zeroed.
|
||||||
u32 zero;
|
u32 zero;
|
||||||
|
@ -40,7 +47,7 @@ namespace europa::structs {
|
||||||
u32 width;
|
u32 width;
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool IsValid() const {
|
[[nodiscard]] constexpr bool IsValid() const {
|
||||||
return magic == ValidMagic;
|
return magic == ValidMagicSF || magic == ValidMagicJSF;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,16 @@ namespace europa::io::yatf {
|
||||||
|
|
||||||
surface.Resize(imageSize);
|
surface.Resize(imageSize);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
auto v1 = [&]() {
|
||||||
using enum structs::YatfHeader::TextureFormat;
|
using enum structs::YatfHeader::TextureFormat;
|
||||||
|
|
||||||
switch(header.format) {
|
switch(header.format) {
|
||||||
case kTextureFormat8Bpp: {
|
case kTextureFormatV1_8Bpp: {
|
||||||
util::Pixel palette[256] {};
|
util::Pixel palette[256] {};
|
||||||
util::UniqueArray<std::uint8_t> palettizedData(imageSize.Linear());
|
util::UniqueArray<std::uint8_t> palettizedData(imageSize.Linear());
|
||||||
|
|
||||||
|
@ -51,7 +58,7 @@ namespace europa::io::yatf {
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case kTextureFormat24Bpp: {
|
case kTextureFormatV1_24Bpp: {
|
||||||
util::UniqueArray<util::PixelRGB> rgbPixelData(imageSize.Linear());
|
util::UniqueArray<util::PixelRGB> rgbPixelData(imageSize.Linear());
|
||||||
stream.read(reinterpret_cast<char*>(&rgbPixelData[0]), imageSize.LinearWithStride<util::PixelRGB>());
|
stream.read(reinterpret_cast<char*>(&rgbPixelData[0]), imageSize.LinearWithStride<util::PixelRGB>());
|
||||||
auto* pDestBuffer = reinterpret_cast<util::Pixel*>(surface.GetBuffer());
|
auto* pDestBuffer = reinterpret_cast<util::Pixel*>(surface.GetBuffer());
|
||||||
|
@ -65,16 +72,96 @@ namespace europa::io::yatf {
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case kTextureFormat32Bpp:
|
case kTextureFormatV1_32Bpp:
|
||||||
// We can directly read data
|
// We can directly read data
|
||||||
stream.read(reinterpret_cast<char*>(surface.GetBuffer()), imageSize.LinearWithStride<util::Pixel>());
|
stream.read(reinterpret_cast<char*>(surface.GetBuffer()), imageSize.LinearWithStride<util::Pixel>());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kTextureFormatUnknown:
|
|
||||||
default:
|
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 texture format {:02x}!", (std::uint16_t)header.format));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto v2 = [&]() {
|
||||||
|
using enum structs::YatfHeader::TextureFormat;
|
||||||
|
|
||||||
|
switch(header.format) {
|
||||||
|
case kTextureFormatV2_4Bpp: {
|
||||||
|
util::Pixel palette[16] {};
|
||||||
|
util::UniqueArray<std::uint8_t> palettizedData(imageSize.Linear() / 2);
|
||||||
|
|
||||||
|
stream.read(reinterpret_cast<char*>(&palette[0]), sizeof(palette));
|
||||||
|
stream.read(reinterpret_cast<char*>(&palettizedData[0]), imageSize.Linear() / 2);
|
||||||
|
|
||||||
|
auto* pDestBuffer = reinterpret_cast<util::Pixel*>(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<std::size_t>(col)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case kTextureFormatV2_8Bpp: {
|
||||||
|
util::Pixel palette[256] {};
|
||||||
|
util::UniqueArray<std::uint8_t> palettizedData(imageSize.Linear());
|
||||||
|
|
||||||
|
stream.read(reinterpret_cast<char*>(&palette[0]), sizeof(palette));
|
||||||
|
stream.read(reinterpret_cast<char*>(&palettizedData[0]), imageSize.Linear());
|
||||||
|
|
||||||
|
auto* pDestBuffer = reinterpret_cast<util::Pixel*>(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<std::size_t>(pp)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case kTextureFormatV2_24Bpp: {
|
||||||
|
util::UniqueArray<util::PixelRGB> rgbPixelData(imageSize.Linear());
|
||||||
|
stream.read(reinterpret_cast<char*>(&rgbPixelData[0]), imageSize.LinearWithStride<util::PixelRGB>());
|
||||||
|
auto* pDestBuffer = reinterpret_cast<util::Pixel*>(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<char*>(surface.GetBuffer()), imageSize.LinearWithStride<util::Pixel>());
|
||||||
|
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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error(std::format("Unknown/unsupported YATF version {:02x}!", (std::uint16_t)header.version));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ int main(int argc, char** argv) {
|
||||||
europa::structs::YatfHeader yatfHeader;
|
europa::structs::YatfHeader yatfHeader;
|
||||||
eutil::ImageSurface surface;
|
eutil::ImageSurface surface;
|
||||||
|
|
||||||
|
|
||||||
|
std::cout << "Opening \"" << argv[1] << "\"\n";
|
||||||
|
|
||||||
if(!reader.ReadImage(yatfHeader, surface)) {
|
if(!reader.ReadImage(yatfHeader, surface)) {
|
||||||
std::cout << "Invalid YATF file \"" << argv[1] << "\"\n";
|
std::cout << "Invalid YATF file \"" << argv[1] << "\"\n";
|
||||||
return 1;
|
return 1;
|
||||||
|
|
Loading…
Reference in a new issue