From c259531d8e9cdf8fcfa8073737aa377be0095b2d Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 2 Jan 2026 19:51:44 +0100 Subject: [PATCH] feat: add external image loader for every supported game --- src/ImageConverter/ImageConverter.cpp | 6 +- src/ObjImage/Image/IwiLoader.cpp | 125 +++++++++++++----- src/ObjImage/Image/IwiLoader.h | 11 +- src/ObjImage/Image/IwiTypes.h | 12 ++ src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 4 +- src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp | 3 +- .../Game/IW5/Image/LoaderImageIW5.cpp | 71 ---------- .../Game/IW5/Image/LoaderImageIW5.h | 13 -- src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 4 +- src/ObjLoading/Game/T5/ObjLoaderT5.cpp | 3 +- .../Game/T6/Image/LoaderImageT6.cpp | 79 ----------- src/ObjLoading/Game/T6/Image/LoaderImageT6.h | 13 -- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 4 +- src/ObjLoading/Image/ImageLoaderCommon.cpp | 74 +++++++++++ src/ObjLoading/Image/ImageLoaderCommon.h | 20 ++- .../Image/ImageLoaderExternal.cpp.template | 111 ++++++++++++++++ .../Image/ImageLoaderExternal.h.template | 23 ++++ .../Game/IW3/Image/ImageDumperIW3.cpp | 3 +- .../Game/IW4/Image/ImageDumperIW4.cpp | 3 +- .../Game/IW5/Image/ImageDumperIW5.cpp | 3 +- .../Game/T5/Image/ImageDumperT5.cpp | 3 +- .../Game/T6/Image/ImageDumperT6.cpp | 9 +- 22 files changed, 369 insertions(+), 228 deletions(-) delete mode 100644 src/ObjLoading/Game/IW5/Image/LoaderImageIW5.cpp delete mode 100644 src/ObjLoading/Game/IW5/Image/LoaderImageIW5.h delete mode 100644 src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp delete mode 100644 src/ObjLoading/Game/T6/Image/LoaderImageT6.h create mode 100644 src/ObjLoading/Image/ImageLoaderExternal.cpp.template create mode 100644 src/ObjLoading/Image/ImageLoaderExternal.h.template diff --git a/src/ImageConverter/ImageConverter.cpp b/src/ImageConverter/ImageConverter.cpp index bdca8853..89cde7f4 100644 --- a/src/ImageConverter/ImageConverter.cpp +++ b/src/ImageConverter/ImageConverter.cpp @@ -79,8 +79,8 @@ namespace return false; } - const auto texture = image::LoadIwi(file); - if (!texture) + const auto loadResult = image::LoadIwi(file); + if (!loadResult) return false; auto outPath = iwiPath; @@ -93,7 +93,7 @@ namespace return false; } - m_dds_writer.DumpImage(outFile, texture.get()); + m_dds_writer.DumpImage(outFile, loadResult->m_texture.get()); return true; } diff --git a/src/ObjImage/Image/IwiLoader.cpp b/src/ObjImage/Image/IwiLoader.cpp index 1893904a..503c804f 100644 --- a/src/ObjImage/Image/IwiLoader.cpp +++ b/src/ObjImage/Image/IwiLoader.cpp @@ -49,17 +49,20 @@ namespace return nullptr; } - std::unique_ptr LoadIwi6(std::istream& stream) + std::optional LoadIwi6(std::istream& stream) { iwi6::IwiHeader header{}; stream.read(reinterpret_cast(&header), sizeof(header)); if (stream.gcount() != sizeof(header)) - return nullptr; + { + con::error("IWI header corrupted"); + return std::nullopt; + } const auto* format = GetFormat6(header.format); if (format == nullptr) - return nullptr; + return std::nullopt; auto width = header.dimensions[0]; auto height = header.dimensions[1]; @@ -88,18 +91,30 @@ namespace && currentFileSize != header.fileSizeForPicmip[currentMipLevel]) { con::error("Iwi has invalid file size for picmip {}", currentMipLevel); - return nullptr; + return std::nullopt; } stream.read(reinterpret_cast(texture->GetBufferForMipLevel(currentMipLevel)), sizeOfMipLevel); if (stream.gcount() != sizeOfMipLevel) { con::error("Unexpected eof of iwi in mip level {}", currentMipLevel); - return nullptr; + return std::nullopt; } } - return texture; + CommonIwiMetaData meta{ + .m_no_picmip = (header.flags & iwi6::IwiFlags::IMG_FLAG_NOPICMIP) != 0, + .m_streaming = (header.flags & iwi6::IwiFlags::IMG_FLAG_STREAMING) != 0, + .m_clamp_u = (header.flags & iwi6::IwiFlags::IMG_FLAG_CLAMP_U) != 0, + .m_clamp_v = (header.flags & iwi6::IwiFlags::IMG_FLAG_CLAMP_V) != 0, + .m_dynamic = (header.flags & iwi6::IwiFlags::IMG_FLAG_DYNAMIC) != 0, + }; + + return IwiLoaderResult{ + .m_version = IwiVersion::IWI_6, + .m_meta = meta, + .m_texture = std::move(texture), + }; } const ImageFormat* GetFormat8(int8_t format) @@ -147,17 +162,20 @@ namespace return nullptr; } - std::unique_ptr LoadIwi8(std::istream& stream) + std::optional LoadIwi8(std::istream& stream) { iwi8::IwiHeader header{}; stream.read(reinterpret_cast(&header), sizeof(header)); if (stream.gcount() != sizeof(header)) - return nullptr; + { + con::error("IWI header corrupted"); + return std::nullopt; + } const auto* format = GetFormat8(header.format); if (format == nullptr) - return nullptr; + return std::nullopt; auto width = header.dimensions[0]; auto height = header.dimensions[1]; @@ -180,12 +198,12 @@ namespace else if ((header.flags & iwi8::IwiFlags::IMG_FLAG_MAPTYPE_MASK) == iwi8::IwiFlags::IMG_FLAG_MAPTYPE_1D) { con::error("Iwi has unsupported map type 1D"); - return nullptr; + return std::nullopt; } else { con::error("Iwi has unsupported map type"); - return nullptr; + return std::nullopt; } texture->Allocate(); @@ -202,18 +220,30 @@ namespace && currentFileSize != header.fileSizeForPicmip[currentMipLevel]) { con::error("Iwi has invalid file size for picmip {}", currentMipLevel); - return nullptr; + return std::nullopt; } stream.read(reinterpret_cast(texture->GetBufferForMipLevel(currentMipLevel)), sizeOfMipLevel); if (stream.gcount() != sizeOfMipLevel) { con::error("Unexpected eof of iwi in mip level {}", currentMipLevel); - return nullptr; + return std::nullopt; } } - return texture; + CommonIwiMetaData meta{ + .m_no_picmip = (header.flags & iwi8::IwiFlags::IMG_FLAG_NOPICMIP) != 0, + .m_streaming = (header.flags & iwi8::IwiFlags::IMG_FLAG_STREAMING) != 0, + .m_clamp_u = (header.flags & iwi8::IwiFlags::IMG_FLAG_CLAMP_U) != 0, + .m_clamp_v = (header.flags & iwi8::IwiFlags::IMG_FLAG_CLAMP_V) != 0, + .m_dynamic = (header.flags & iwi8::IwiFlags::IMG_FLAG_DYNAMIC) != 0, + }; + + return IwiLoaderResult{ + .m_version = IwiVersion::IWI_8, + .m_meta = meta, + .m_texture = std::move(texture), + }; } const ImageFormat* GetFormat13(int8_t format) @@ -258,17 +288,20 @@ namespace return nullptr; } - std::unique_ptr LoadIwi13(std::istream& stream) + std::optional LoadIwi13(std::istream& stream) { iwi13::IwiHeader header{}; stream.read(reinterpret_cast(&header), sizeof(header)); if (stream.gcount() != sizeof(header)) - return nullptr; + { + con::error("IWI header corrupted"); + return std::nullopt; + } const auto* format = GetFormat6(header.format); if (format == nullptr) - return nullptr; + return std::nullopt; auto width = header.dimensions[0]; auto height = header.dimensions[1]; @@ -297,18 +330,31 @@ namespace && currentFileSize != header.fileSizeForPicmip[currentMipLevel]) { con::error("Iwi has invalid file size for picmip {}", currentMipLevel); - return nullptr; + return std::nullopt; } stream.read(reinterpret_cast(texture->GetBufferForMipLevel(currentMipLevel)), sizeOfMipLevel); if (stream.gcount() != sizeOfMipLevel) { con::error("Unexpected eof of iwi in mip level {}", currentMipLevel); - return nullptr; + return std::nullopt; } } - return texture; + CommonIwiMetaData meta{ + .m_no_picmip = (header.flags & iwi13::IwiFlags::IMG_FLAG_NOPICMIP) != 0, + .m_streaming = (header.flags & iwi13::IwiFlags::IMG_FLAG_STREAMING) != 0, + .m_clamp_u = (header.flags & iwi13::IwiFlags::IMG_FLAG_CLAMP_U) != 0, + .m_clamp_v = (header.flags & iwi13::IwiFlags::IMG_FLAG_CLAMP_V) != 0, + .m_dynamic = (header.flags & iwi13::IwiFlags::IMG_FLAG_DYNAMIC) != 0, + .m_gamma = header.gamma, + }; + + return IwiLoaderResult{ + .m_version = IwiVersion::IWI_13, + .m_meta = meta, + .m_texture = std::move(texture), + }; } const ImageFormat* GetFormat27(int8_t format) @@ -355,17 +401,20 @@ namespace return nullptr; } - std::unique_ptr LoadIwi27(std::istream& stream) + std::optional LoadIwi27(std::istream& stream) { iwi27::IwiHeader header{}; stream.read(reinterpret_cast(&header), sizeof(header)); if (stream.gcount() != sizeof(header)) - return nullptr; + { + con::error("IWI header corrupted"); + return std::nullopt; + } const auto* format = GetFormat27(header.format); if (format == nullptr) - return nullptr; + return std::nullopt; auto width = header.dimensions[0]; auto height = header.dimensions[1]; @@ -394,35 +443,51 @@ namespace && currentFileSize != header.fileSizeForPicmip[currentMipLevel]) { con::error("Iwi has invalid file size for picmip {}", currentMipLevel); - return nullptr; + return std::nullopt; } stream.read(reinterpret_cast(texture->GetBufferForMipLevel(currentMipLevel)), sizeOfMipLevel); if (stream.gcount() != sizeOfMipLevel) { con::error("Unexpected eof of iwi in mip level {}", currentMipLevel); - return nullptr; + return std::nullopt; } } - return texture; + CommonIwiMetaData meta{ + .m_no_picmip = (header.flags & iwi27::IwiFlags::IMG_FLAG_NOPICMIP) != 0, + .m_streaming = (header.flags & iwi27::IwiFlags::IMG_FLAG_STREAMING) != 0, + .m_clamp_u = (header.flags & iwi27::IwiFlags::IMG_FLAG_CLAMP_U) != 0, + .m_clamp_v = (header.flags & iwi27::IwiFlags::IMG_FLAG_CLAMP_V) != 0, + .m_dynamic = (header.flags & iwi27::IwiFlags::IMG_FLAG_DYNAMIC) != 0, + .m_gamma = header.gamma, + }; + + return IwiLoaderResult{ + .m_version = IwiVersion::IWI_27, + .m_meta = meta, + .m_texture = std::move(texture), + }; } } // namespace namespace image { - std::unique_ptr LoadIwi(std::istream& stream) + std::optional LoadIwi(std::istream& stream) { IwiVersionHeader iwiVersionHeader{}; stream.read(reinterpret_cast(&iwiVersionHeader), sizeof(iwiVersionHeader)); if (stream.gcount() != sizeof(iwiVersionHeader)) - return nullptr; + { + con::error("IWI version header corrupted"); + return std::nullopt; + } if (iwiVersionHeader.tag[0] != 'I' || iwiVersionHeader.tag[1] != 'W' || iwiVersionHeader.tag[2] != 'i') { con::error("Invalid IWI magic"); - return nullptr; + return std::nullopt; } switch (iwiVersionHeader.version) @@ -444,6 +509,6 @@ namespace image } con::error("Unknown IWI version {}", iwiVersionHeader.version); - return nullptr; + return std::nullopt; } } // namespace image diff --git a/src/ObjImage/Image/IwiLoader.h b/src/ObjImage/Image/IwiLoader.h index 29aaeb61..66b42fd1 100644 --- a/src/ObjImage/Image/IwiLoader.h +++ b/src/ObjImage/Image/IwiLoader.h @@ -1,11 +1,20 @@ #pragma once +#include "Image/IwiTypes.h" #include "Image/Texture.h" #include #include +#include namespace image { - std::unique_ptr LoadIwi(std::istream& stream); + struct IwiLoaderResult + { + IwiVersion m_version; + CommonIwiMetaData m_meta; + std::unique_ptr m_texture; + }; + + std::optional LoadIwi(std::istream& stream); }; // namespace image diff --git a/src/ObjImage/Image/IwiTypes.h b/src/ObjImage/Image/IwiTypes.h index 37eb04e9..2a26974f 100644 --- a/src/ObjImage/Image/IwiTypes.h +++ b/src/ObjImage/Image/IwiTypes.h @@ -16,6 +16,18 @@ namespace image IWI_27 = 27 }; + struct CommonIwiMetaData + { + // Always high resolution + bool m_no_picmip; + bool m_streaming; + bool m_clamp_u; + bool m_clamp_v; + bool m_dynamic; + + float m_gamma; + }; + struct IwiVersionHeader { char tag[3]; diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index 4052f23c..913d7d3b 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -4,8 +4,8 @@ #include "Game/IW3/AssetMarkerIW3.h" #include "Game/IW3/GameIW3.h" #include "Game/IW3/IW3.h" +#include "Game/IW3/Image/ImageLoaderExternalIW3.h" #include "Game/IW3/XModel/LoaderXModelIW3.h" -#include "Image/AssetLoaderImageIW3.h" #include "Localize/AssetLoaderLocalizeIW3.h" #include "Material/LoaderMaterialIW3.h" #include "ObjLoading.h" @@ -95,7 +95,7 @@ namespace collection.AddAssetCreator(xmodel::CreateLoaderIW3(memory, searchPath, zone)); collection.AddAssetCreator(material::CreateLoaderIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - collection.AddAssetCreator(image::CreateLoaderIW3(memory, searchPath)); + collection.AddAssetCreator(image::CreateLoaderExternalIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp index 5558b40a..c4b6e94b 100644 --- a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp +++ b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp @@ -4,6 +4,7 @@ #include "Game/IW4/AssetMarkerIW4.h" #include "Game/IW4/GameIW4.h" #include "Game/IW4/IW4.h" +#include "Game/IW4/Image/ImageLoaderExternalIW4.h" #include "Game/IW4/XModel/LoaderXModelIW4.h" #include "Leaderboard/LoaderLeaderboardIW4.h" #include "LightDef/LightDefLoaderIW4.h" @@ -130,7 +131,7 @@ namespace collection.AddAssetCreator(shader::CreatePixelShaderLoaderIW4(memory, searchPath)); collection.AddAssetCreator(shader::CreateVertexShaderLoaderIW4(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(image::CreateLoaderExternalIW4(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(sound_curve::CreateLoaderIW4(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.cpp b/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.cpp deleted file mode 100644 index d51d670b..00000000 --- a/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "LoaderImageIW5.h" - -#include "Game/IW5/IW5.h" -#include "Image/ImageCommon.h" -#include "Image/IwiLoader.h" -#include "Utils/Logging/Log.h" - -#include -#include -#include -#include - -using namespace IW5; - -namespace -{ - constexpr auto MAX_IMAGE_NAME_SIZE = 0x800; - - class ImageLoader final : public AssetCreator - { - public: - ImageLoader(MemoryManager& memory, ISearchPath& searchPath) - : m_memory(memory), - m_search_path(searchPath) - { - } - - AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override - { - const auto fileName = image::GetFileNameForAsset(assetName, ".iwi"); - const auto file = m_search_path.Open(fileName); - if (!file.IsOpen()) - return AssetCreationResult::NoAction(); - - const auto fileSize = static_cast(file.m_length); - const auto fileData = std::make_unique(fileSize); - file.m_stream->read(fileData.get(), fileSize); - - std::istringstream ss(std::string(fileData.get(), fileSize)); - const auto texture = image::LoadIwi(ss); - if (!texture) - { - con::error("Failed to load texture from: {}", fileName); - return AssetCreationResult::Failure(); - } - - auto* image = m_memory.Alloc(); - image->name = m_memory.Dup(assetName.c_str()); - image->noPicmip = !texture->HasMipMaps(); - image->width = static_cast(texture->GetWidth()); - image->height = static_cast(texture->GetHeight()); - image->depth = static_cast(texture->GetDepth()); - - image->texture.loadDef = m_memory.Alloc(); - - return AssetCreationResult::Success(context.AddAsset(assetName, image)); - } - - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - }; -} // namespace - -namespace image -{ - std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath) - { - return std::make_unique(memory, searchPath); - } -} // namespace image diff --git a/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.h b/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.h deleted file mode 100644 index b95a8f23..00000000 --- a/src/ObjLoading/Game/IW5/Image/LoaderImageIW5.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "Asset/IAssetCreator.h" -#include "Game/IW5/IW5.h" -#include "SearchPath/ISearchPath.h" -#include "Utils/MemoryManager.h" - -#include - -namespace image -{ - std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath); -} // namespace image diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index 70079f7b..55b1f684 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -4,8 +4,8 @@ #include "Game/IW5/AssetMarkerIW5.h" #include "Game/IW5/GameIW5.h" #include "Game/IW5/IW5.h" +#include "Game/IW5/Image/ImageLoaderExternalIW5.h" #include "Game/IW5/XModel/LoaderXModelIW5.h" -#include "Image/LoaderImageIW5.h" #include "Leaderboard/LoaderLeaderboardIW5.h" #include "Localize/LoaderLocalizeIW5.h" #include "Material/LoaderMaterialIW5.h" @@ -132,7 +132,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - collection.AddAssetCreator(image::CreateLoaderIW5(memory, searchPath)); + collection.AddAssetCreator(image::CreateLoaderExternalIW5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index e8cff138..b897b567 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -3,6 +3,7 @@ #include "Asset/GlobalAssetPoolsLoader.h" #include "Game/T5/AssetMarkerT5.h" #include "Game/T5/GameT5.h" +#include "Game/T5/Image/ImageLoaderExternalT5.h" #include "Game/T5/T5.h" #include "Game/T5/XModel/LoaderXModelT5.h" #include "Localize/LoaderLocalizeT5.h" @@ -108,7 +109,7 @@ namespace collection.AddAssetCreator(xmodel::CreateLoaderT5(memory, searchPath, zone)); collection.AddAssetCreator(material::CreateLoaderT5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(image::CreateLoaderExternalT5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp b/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp deleted file mode 100644 index 81ffbf7c..00000000 --- a/src/ObjLoading/Game/T6/Image/LoaderImageT6.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "LoaderImageT6.h" - -#include "Game/T6/CommonT6.h" -#include "Game/T6/T6.h" -#include "Image/ImageCommon.h" -#include "Image/IwiLoader.h" -#include "Utils/Logging/Log.h" - -#include -#include -#include -#include -#include - -using namespace T6; - -namespace -{ - class ImageLoader final : public AssetCreator - { - public: - ImageLoader(MemoryManager& memory, ISearchPath& searchPath) - : m_memory(memory), - m_search_path(searchPath) - { - } - - AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override - { - const auto fileName = image::GetFileNameForAsset(assetName, ".iwi"); - const auto file = m_search_path.Open(fileName); - if (!file.IsOpen()) - return AssetCreationResult::NoAction(); - - const auto fileSize = static_cast(file.m_length); - const auto fileData = std::make_unique(fileSize); - file.m_stream->read(fileData.get(), static_cast(fileSize)); - const auto dataHash = static_cast(crc32(0u, reinterpret_cast(fileData.get()), static_cast(fileSize))); - - std::istringstream ss(std::string(fileData.get(), fileSize)); - const auto texture = image::LoadIwi(ss); - if (!texture) - { - con::error("Failed to load texture from: {}", fileName); - return AssetCreationResult::Failure(); - } - - auto* image = m_memory.Alloc(); - image->name = m_memory.Dup(assetName.c_str()); - image->hash = Common::R_HashString(image->name, 0); - image->delayLoadPixels = true; - - image->noPicmip = !texture->HasMipMaps(); - image->width = static_cast(texture->GetWidth()); - image->height = static_cast(texture->GetHeight()); - image->depth = static_cast(texture->GetDepth()); - - image->streaming = 1; - image->streamedParts[0].levelCount = 1; - image->streamedParts[0].levelSize = static_cast(fileSize); - image->streamedParts[0].hash = dataHash & 0x1FFFFFFF; - image->streamedPartCount = 1; - - return AssetCreationResult::Success(context.AddAsset(assetName, image)); - } - - private: - MemoryManager& m_memory; - ISearchPath& m_search_path; - }; -} // namespace - -namespace image -{ - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath) - { - return std::make_unique(memory, searchPath); - } -} // namespace image diff --git a/src/ObjLoading/Game/T6/Image/LoaderImageT6.h b/src/ObjLoading/Game/T6/Image/LoaderImageT6.h deleted file mode 100644 index 610af102..00000000 --- a/src/ObjLoading/Game/T6/Image/LoaderImageT6.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "Asset/IAssetCreator.h" -#include "Game/T6/T6.h" -#include "SearchPath/ISearchPath.h" -#include "Utils/MemoryManager.h" - -#include - -namespace image -{ - std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath); -} // namespace image diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index a7df0b3f..716f5406 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -7,12 +7,12 @@ #include "Game/T6/CommonT6.h" #include "Game/T6/GameAssetPoolT6.h" #include "Game/T6/GameT6.h" +#include "Game/T6/Image/ImageLoaderExternalT6.h" #include "Game/T6/T6.h" #include "Game/T6/XModel/LoaderXModelT6.h" #include "Image/Dx12TextureLoader.h" #include "Image/IwiLoader.h" #include "Image/IwiTypes.h" -#include "Image/LoaderImageT6.h" #include "Image/Texture.h" #include "Leaderboard/JsonLoaderLeaderboardT6.h" #include "Localize/LocalizeLoaderT6.h" @@ -394,7 +394,7 @@ namespace T6 collection.AddAssetCreator(xmodel::CreateLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(material::CreateLoaderT6(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - collection.AddAssetCreator(image::CreateLoaderT6(memory, searchPath)); + collection.AddAssetCreator(image::CreateLoaderExternalT6(memory, searchPath)); collection.AddAssetCreator(sound::CreateSoundBankLoaderT6(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Image/ImageLoaderCommon.cpp b/src/ObjLoading/Image/ImageLoaderCommon.cpp index 6f0c3339..569b964b 100644 --- a/src/ObjLoading/Image/ImageLoaderCommon.cpp +++ b/src/ObjLoading/Image/ImageLoaderCommon.cpp @@ -1,5 +1,43 @@ #include "ImageLoaderCommon.h" +#include "Image/ImageCommon.h" +#include "Image/IwiLoader.h" +#include "Image/Texture.h" +#include "Utils/Logging/Log.h" + +#include +#include + +using namespace image; + +namespace +{ + CommonImageLoaderResult Success(size_t iwiSize, CommonIwiMetaData meta, std::unique_ptr texture, CommonImageLoaderHash hash) + { + return CommonImageLoaderResult{ + .m_failure = false, + .m_iwi_size = iwiSize, + .m_meta = meta, + .m_texture = std::move(texture), + .m_hash = hash, + }; + } + + CommonImageLoaderResult NoAction() + { + return CommonImageLoaderResult{ + .m_failure = false, + }; + } + + CommonImageLoaderResult Failure() + { + return CommonImageLoaderResult{ + .m_failure = true, + }; + } +} // namespace + namespace image { std::optional CommonImageLoaderResult::GetResultIfCancelled() const @@ -12,4 +50,40 @@ namespace image return std::nullopt; } + + CommonImageLoaderResult + LoadImageCommon(const std::string& imageName, ISearchPath& searchPath, IwiVersion expectedIwiVersion, CommonImageLoaderHashType hashType) + { + const auto fileName = image::GetFileNameForAsset(imageName, ".iwi"); + const auto file = searchPath.Open(fileName); + if (!file.IsOpen()) + return NoAction(); + + const auto fileSize = static_cast(file.m_length); + + std::optional loaderResult; + CommonImageLoaderHash hash{}; + if (hashType == CommonImageLoaderHashType::NONE) + { + loaderResult = image::LoadIwi(*file.m_stream); + } + else + { + const auto fileData = std::make_unique(fileSize); + file.m_stream->read(fileData.get(), static_cast(fileSize)); + + hash.crc32 = static_cast(crc32(0u, reinterpret_cast(fileData.get()), static_cast(fileSize))); + + std::istringstream inMemory(std::string(fileData.get(), fileSize)); + loaderResult = image::LoadIwi(inMemory); + } + + if (!loaderResult) + { + con::error("Failed to load texture from: {}", fileName); + return Failure(); + } + + return Success(fileSize, loaderResult->m_meta, std::move(loaderResult->m_texture), hash); + } } // namespace image diff --git a/src/ObjLoading/Image/ImageLoaderCommon.h b/src/ObjLoading/Image/ImageLoaderCommon.h index 57744ae2..a4214603 100644 --- a/src/ObjLoading/Image/ImageLoaderCommon.h +++ b/src/ObjLoading/Image/ImageLoaderCommon.h @@ -3,21 +3,37 @@ #include "Asset/AssetCreationResult.h" #include "Image/IwiTypes.h" #include "Image/Texture.h" +#include "SearchPath/ISearchPath.h" +#include #include #include #include namespace image { + enum class CommonImageLoaderHashType + { + NONE, + CRC32 + }; + + union CommonImageLoaderHash + { + uint32_t crc32; + }; + struct CommonImageLoaderResult { std::optional GetResultIfCancelled() const; bool m_failure; - std::string m_iwi_path; + size_t m_iwi_size; + CommonIwiMetaData m_meta; std::unique_ptr m_texture; + CommonImageLoaderHash m_hash; }; - CommonImageLoaderResult LoadImageCommon(); + CommonImageLoaderResult + LoadImageCommon(const std::string& imageName, ISearchPath& searchPath, IwiVersion expectedIwiVersion, CommonImageLoaderHashType hashType); } // namespace image diff --git a/src/ObjLoading/Image/ImageLoaderExternal.cpp.template b/src/ObjLoading/Image/ImageLoaderExternal.cpp.template new file mode 100644 index 00000000..81321baf --- /dev/null +++ b/src/ObjLoading/Image/ImageLoaderExternal.cpp.template @@ -0,0 +1,111 @@ +#options GAME (IW3, IW4, IW5, T5, T6) + +#filename "Game/" + GAME + "/Image/ImageLoaderExternal" + GAME + ".cpp" + +#if GAME == "IW3" +#define FEATURE_IW3 +#elif GAME == "IW4" +#define FEATURE_IW4 +#elif GAME == "IW5" +#define FEATURE_IW5 +#elif GAME == "T5" +#define FEATURE_T5 +#elif GAME == "T6" +#define FEATURE_T6 +#endif + +#if defined(FEATURE_IW3) +#define IWI_VERSION IWI_6 +#elif defined(FEATURE_IW4) || defined(FEATURE_IW5) +#define IWI_VERSION IWI_8 +#elif defined(FEATURE_T5) +#define IWI_VERSION IWI_13 +#elif defined(FEATURE_T6) +#define IWI_VERSION IWI_27 +#endif + +#if defined(FEATURE_T6) +#define HASH_TYPE CRC32 +#else +#define HASH_TYPE NONE +#endif + + +// This file was templated. +// See ImageLoaderExternal.cpp.template. +// Do not modify, changes will be lost. + +#set LOADER_HEADER "\"ImageLoaderExternal" + GAME + ".h\"" +#include LOADER_HEADER + +#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\"" +#include COMMON_HEADER +#include "Image/ImageLoaderCommon.h" +#include "Utils/Logging/Log.h" + +#include + +using namespace GAME; +using namespace image; + +namespace +{ +#set LOADER_CLASS "ImageLoader" + GAME + class LOADER_CLASS final : public AssetCreator + { + public: + LOADER_CLASS(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto loadingResult = LoadImageCommon(assetName, m_search_path, IwiVersion::IWI_VERSION, CommonImageLoaderHashType::HASH_TYPE); + const auto earlyReturn = loadingResult.GetResultIfCancelled(); + if (earlyReturn) + return *earlyReturn; + + const auto* texture = loadingResult.m_texture.get(); + + auto* image = m_memory.Alloc(); + image->name = m_memory.Dup(assetName.c_str()); +#ifdef FEATURE_t6 + image->hash = Common::R_HashString(image->name, 0); +#endif +#ifndef FEATURE_IW5 + image->delayLoadPixels = true; +#endif + + image->noPicmip = loadingResult.m_meta.m_no_picmip; + image->width = static_cast(texture->GetWidth()); + image->height = static_cast(texture->GetHeight()); + image->depth = static_cast(texture->GetDepth()); + +#ifdef FEATURE_T6 + image->streaming = 1; + image->streamedParts[0].levelCount = 1; + image->streamedParts[0].levelSize = static_cast(loadingResult.m_iwi_size); + image->streamedParts[0].hash = loadingResult.m_hash.crc32 & 0x1FFFFFFF; + image->streamedPartCount = 1; +#endif + + image->texture.loadDef = m_memory.Alloc(); + + return AssetCreationResult::Success(context.AddAsset(assetName, image)); + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace image +{ +#set LOADER_METHOD "CreateLoaderExternal" + GAME + std::unique_ptr> LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace image diff --git a/src/ObjLoading/Image/ImageLoaderExternal.h.template b/src/ObjLoading/Image/ImageLoaderExternal.h.template new file mode 100644 index 00000000..860619bd --- /dev/null +++ b/src/ObjLoading/Image/ImageLoaderExternal.h.template @@ -0,0 +1,23 @@ +#options GAME (IW3, IW4, IW5, T5, T6) + +#filename "Game/" + GAME + "/Image/ImageLoaderExternal" + GAME + ".h" + +// This file was templated. +// See ImageLoaderExternal.h.template. +// Do not modify, changes will be lost. + +#pragma once + +#include "Asset/IAssetCreator.h" +#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\"" +#include GAME_HEADER +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace image +{ +#set LOADER_METHOD "CreateLoaderExternal" + GAME + std::unique_ptr> LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath); +} // namespace image diff --git a/src/ObjWriting/Game/IW3/Image/ImageDumperIW3.cpp b/src/ObjWriting/Game/IW3/Image/ImageDumperIW3.cpp index 3e9d81f9..f73d88d7 100644 --- a/src/ObjWriting/Game/IW3/Image/ImageDumperIW3.cpp +++ b/src/ObjWriting/Game/IW3/Image/ImageDumperIW3.cpp @@ -49,7 +49,8 @@ namespace return nullptr; } - return image::LoadIwi(*filePathImage.m_stream); + auto loadResult = image::LoadIwi(*filePathImage.m_stream); + return loadResult ? std::move(loadResult->m_texture) : nullptr; } std::unique_ptr LoadImageData(ISearchPath& searchPath, const GfxImage& image) diff --git a/src/ObjWriting/Game/IW4/Image/ImageDumperIW4.cpp b/src/ObjWriting/Game/IW4/Image/ImageDumperIW4.cpp index 1327e43a..e52aacd3 100644 --- a/src/ObjWriting/Game/IW4/Image/ImageDumperIW4.cpp +++ b/src/ObjWriting/Game/IW4/Image/ImageDumperIW4.cpp @@ -46,7 +46,8 @@ namespace return nullptr; } - return image::LoadIwi(*filePathImage.m_stream); + auto loadResult = image::LoadIwi(*filePathImage.m_stream); + return loadResult ? std::move(loadResult->m_texture) : nullptr; } std::unique_ptr LoadImageData(ISearchPath& searchPath, const GfxImage& image) diff --git a/src/ObjWriting/Game/IW5/Image/ImageDumperIW5.cpp b/src/ObjWriting/Game/IW5/Image/ImageDumperIW5.cpp index 71f2f69e..74995f30 100644 --- a/src/ObjWriting/Game/IW5/Image/ImageDumperIW5.cpp +++ b/src/ObjWriting/Game/IW5/Image/ImageDumperIW5.cpp @@ -47,7 +47,8 @@ namespace return nullptr; } - return image::LoadIwi(*filePathImage.m_stream); + auto loadResult = image::LoadIwi(*filePathImage.m_stream); + return loadResult ? std::move(loadResult->m_texture) : nullptr; } std::unique_ptr LoadImageData(ISearchPath& searchPath, const GfxImage& image) diff --git a/src/ObjWriting/Game/T5/Image/ImageDumperT5.cpp b/src/ObjWriting/Game/T5/Image/ImageDumperT5.cpp index 88a9cdd8..2f0e0649 100644 --- a/src/ObjWriting/Game/T5/Image/ImageDumperT5.cpp +++ b/src/ObjWriting/Game/T5/Image/ImageDumperT5.cpp @@ -46,7 +46,8 @@ namespace return nullptr; } - return image::LoadIwi(*filePathImage.m_stream); + auto loadResult = image::LoadIwi(*filePathImage.m_stream); + return loadResult ? std::move(loadResult->m_texture) : nullptr; } std::unique_ptr LoadImageData(ISearchPath& searchPath, const GfxImage& image) diff --git a/src/ObjWriting/Game/T6/Image/ImageDumperT6.cpp b/src/ObjWriting/Game/T6/Image/ImageDumperT6.cpp index 29b64061..61914b4a 100644 --- a/src/ObjWriting/Game/T6/Image/ImageDumperT6.cpp +++ b/src/ObjWriting/Game/T6/Image/ImageDumperT6.cpp @@ -47,11 +47,11 @@ namespace if (ipakStream) { - auto loadedTexture = image::LoadIwi(*ipakStream); + auto loadResult = image::LoadIwi(*ipakStream); ipakStream->close(); - if (loadedTexture != nullptr) - return loadedTexture; + if (loadResult) + return std::move(loadResult->m_texture); } } } @@ -64,7 +64,8 @@ namespace return nullptr; } - return image::LoadIwi(*filePathImage.m_stream); + auto loadResult = image::LoadIwi(*filePathImage.m_stream); + return loadResult ? std::move(loadResult->m_texture) : nullptr; } std::unique_ptr LoadImageData(ISearchPath& searchPath, const GfxImage& image)