From 6d91713f7185a84013a3b98e4c995a27656ac1af Mon Sep 17 00:00:00 2001 From: craftablescience Date: Sun, 7 Jul 2024 18:06:57 -0400 Subject: [PATCH] vpkpp: add rtx remix pkg support --- THIRDPARTY_LEGAL_NOTICES.txt | 72 ++++++ ext/_ext.cmake | 6 + include/vpkpp/format/PKG.h | 82 ++++++ include/vpkpp/vpkpp.h | 1 + src/vpkpp/_vpkpp.cmake | 4 +- src/vpkpp/format/GMA.cpp | 2 +- src/vpkpp/format/PKG.cpp | 476 +++++++++++++++++++++++++++++++++++ 7 files changed, 641 insertions(+), 2 deletions(-) create mode 100644 include/vpkpp/format/PKG.h create mode 100644 src/vpkpp/format/PKG.cpp diff --git a/THIRDPARTY_LEGAL_NOTICES.txt b/THIRDPARTY_LEGAL_NOTICES.txt index 3197581f5..20788f9f8 100644 --- a/THIRDPARTY_LEGAL_NOTICES.txt +++ b/THIRDPARTY_LEGAL_NOTICES.txt @@ -82,6 +82,31 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------- dxvk-remix --------------- + +MIT License + +Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + --------------- emscripten --------------- MIT License @@ -107,6 +132,26 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------- GDeflate --------------- + +Apache License, Version 2.0 + +Copyright (c) 2020, 2021, 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + --------------- half --------------- MIT License @@ -225,6 +270,33 @@ Julian Seward, jseward@acm.org bzip2/libbzip2 version 1.1.0 of 6 September 2010 +--------------- libdeflate --------------- + +MIT License + +Copyright 2016 Eric Biggers +Copyright 2018 Eric Biggers +Copyright 2017 Jun He + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + --------------- liblzma --------------- BSD Zero Clause License diff --git a/ext/_ext.cmake b/ext/_ext.cmake index d2153d158..137095cb1 100644 --- a/ext/_ext.cmake +++ b/ext/_ext.cmake @@ -31,6 +31,12 @@ if(SOURCEPP_USE_VPKPP) endif() +# gdeflate +if(SOURCEPP_USE_VPKPP) + add_sourcepp_remote_library(gdeflate https://github.com/craftablescience/gdeflate 315d565f65b41791eaf9504fae41f0208929f950) +endif() + + # ice if(SOURCEPP_USE_VCRYPTPP) add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ice") diff --git a/include/vpkpp/format/PKG.h b/include/vpkpp/format/PKG.h new file mode 100644 index 000000000..b8bbce656 --- /dev/null +++ b/include/vpkpp/format/PKG.h @@ -0,0 +1,82 @@ +/* + * AssetDesc and BlobDesc structs are taken from the RTX Remix source code. + * https://github.com/NVIDIAGameWorks/dxvk-remix/tree/main/src/dxvk/rtx_render/rtx_asset_package.h + * See THIRDPARTY_LEGAL_NOTICES.txt for more information. + */ + +#pragma once + +#include "../PackFile.h" + +namespace vpkpp { + +constexpr uint32_t PKG_SIGNATURE = 0xbaadd00d; +constexpr std::string_view PKG_EXTENSION = ".pkg"; + +class PKG : public PackFileReadOnly { +protected: + struct Asset { + uint16_t nameIndex; + enum class Type : uint8_t { + UNKNOWN, + IMAGE_1D, + IMAGE_2D, + IMAGE_3D, + IMAGE_CUBE, + BUFFER, + } type; + uint8_t format; // VkFormat + uint16_t width; + uint16_t height; + uint16_t depth; + uint16_t mipCount; + uint16_t tailMipCount; + uint16_t arraySize; + uint16_t startBlobIndex; + uint16_t endBlobIndex; + + [[nodiscard]] uint32_t getBlobIndex(int frame, int face, int mip) const; + }; + + struct Blob { + uint64_t offset; + enum class Compression : uint8_t { + NONE, + GDEFLATE, + } compression; + uint8_t flags; + uint32_t size; + uint32_t crc32; + }; + + struct Archive { + std::vector assets; + std::vector blobs; + }; + +public: + /// Open a PKG file + [[nodiscard]] static std::unique_ptr open(const std::string& path, const EntryCallback& callback = nullptr); + + [[nodiscard]] std::optional> readEntry(const std::string& path_) const override; + + [[nodiscard]] constexpr bool isCaseSensitive() const override { + return true; + } + + [[nodiscard]] std::string getTruncatedFilestem() const override; + + [[nodiscard]] Attribute getSupportedEntryAttributes() const override; + +protected: + using PackFileReadOnly::PackFileReadOnly; + + [[nodiscard]] bool openNumbered(uint16_t archiveIndex, const std::string& path, const EntryCallback& callback); + + std::unordered_map metadata; + +private: + VPKPP_REGISTER_PACKFILE_OPEN(PKG_EXTENSION, &PKG::open); +}; + +} // namespace vpkpp diff --git a/include/vpkpp/vpkpp.h b/include/vpkpp/vpkpp.h index 7fe67272a..3264c9f73 100644 --- a/include/vpkpp/vpkpp.h +++ b/include/vpkpp/vpkpp.h @@ -16,6 +16,7 @@ #include "format/ORE.h" #include "format/PAK.h" #include "format/PCK.h" +#include "format/PKG.h" #include "format/VPK.h" #include "format/VPK_VTMB.h" #include "format/VPP.h" diff --git a/src/vpkpp/_vpkpp.cmake b/src/vpkpp/_vpkpp.cmake index 60d305aff..e66b5d3b4 100644 --- a/src/vpkpp/_vpkpp.cmake +++ b/src/vpkpp/_vpkpp.cmake @@ -1,5 +1,5 @@ add_pretty_parser(vpkpp - DEPS libzstd_static miniz MINIZIP::minizip sourcepp_crypto sourcepp_parser sourcepp::kvpp + DEPS gdeflate libzstd_static miniz MINIZIP::minizip sourcepp_crypto sourcepp_parser sourcepp::kvpp DEPS_PUBLIC tsl::hat_trie PRECOMPILED_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/APK.h" @@ -13,6 +13,7 @@ add_pretty_parser(vpkpp "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/ORE.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PAK.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PCK.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PKG.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK_VTMB.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPP.h" @@ -36,6 +37,7 @@ add_pretty_parser(vpkpp "${CMAKE_CURRENT_LIST_DIR}/format/ORE.cpp" "${CMAKE_CURRENT_LIST_DIR}/format/PAK.cpp" "${CMAKE_CURRENT_LIST_DIR}/format/PCK.cpp" + "${CMAKE_CURRENT_LIST_DIR}/format/PKG.cpp" "${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp" "${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp" "${CMAKE_CURRENT_LIST_DIR}/format/VPP.cpp" diff --git a/src/vpkpp/format/GMA.cpp b/src/vpkpp/format/GMA.cpp index 3f7e0821c..7b63c246c 100644 --- a/src/vpkpp/format/GMA.cpp +++ b/src/vpkpp/format/GMA.cpp @@ -39,7 +39,7 @@ std::unique_ptr GMA::open(const std::string& path, const EntryCallback while (reader.read() > 0) { Entry entry = createNewEntry(); - auto entryPath = reader.read_string(); + auto entryPath = gma->cleanEntryPath(reader.read_string()); entry.length = reader.read(); reader.read(entry.crc32); diff --git a/src/vpkpp/format/PKG.cpp b/src/vpkpp/format/PKG.cpp new file mode 100644 index 000000000..d9e552f40 --- /dev/null +++ b/src/vpkpp/format/PKG.cpp @@ -0,0 +1,476 @@ +// ReSharper disable CppRedundantParentheses +// ReSharper disable CppRedundantQualifier + +#include + +#include + +#include +#include +#include + +using namespace sourcepp; +using namespace vpkpp; + +namespace { + +// Convert some Vulkan 1.0 formats to corresponding DXGI formats +[[nodiscard]] constexpr uint32_t mapVkFormatToDXGIFormat(uint32_t format) { + switch (format) { + case 3 /*VK_FORMAT_B4G4R4A4_UNORM_PACK16*/: return 115 /*DXGI_FORMAT_B4G4R4A4_UNORM*/; + case 7 /*VK_FORMAT_B5G5R5A1_UNORM_PACK16*/: return 86 /*DXGI_FORMAT_B5G5R5A1_UNORM*/; + case 9 /*VK_FORMAT_R8_UNORM*/: return 61 /*DXGI_FORMAT_R8_UNORM*/; + case 10 /*VK_FORMAT_R8_SNORM*/: return 63 /*DXGI_FORMAT_R8_SNORM*/; + case 11 /*VK_FORMAT_R8_USCALED*/: return 62 /*DXGI_FORMAT_R8_UINT*/; + case 12 /*VK_FORMAT_R8_SSCALED*/: return 64 /*DXGI_FORMAT_R8_SINT*/; + case 13 /*VK_FORMAT_R8_UINT*/: return 62 /*DXGI_FORMAT_R8_UINT*/; + case 14 /*VK_FORMAT_R8_SINT*/: return 64 /*DXGI_FORMAT_R8_SINT*/; + case 16 /*VK_FORMAT_R8G8_UNORM*/: return 49 /*DXGI_FORMAT_R8G8_UNORM*/; + case 17 /*VK_FORMAT_R8G8_SNORM*/: return 51 /*DXGI_FORMAT_R8G8_SNORM*/; + case 18 /*VK_FORMAT_R8G8_USCALED*/: return 49 /*DXGI_FORMAT_R8G8_UNORM*/; + case 19 /*VK_FORMAT_R8G8_SSCALED*/: return 51 /*DXGI_FORMAT_R8G8_SNORM*/; + case 20 /*VK_FORMAT_R8G8_UINT*/: return 50 /*DXGI_FORMAT_R8G8_UINT*/; + case 21 /*VK_FORMAT_R8G8_SINT*/: return 52 /*DXGI_FORMAT_R8G8_SINT*/; + case 37 /*VK_FORMAT_R8G8B8A8_UNORM*/: return 28 /*DXGI_FORMAT_R8G8B8A8_UNORM*/; + case 38 /*VK_FORMAT_R8G8B8A8_SNORM*/: return 31 /*DXGI_FORMAT_R8G8B8A8_SNORM*/; + case 39 /*VK_FORMAT_R8G8B8A8_USCALED*/: return 30 /*DXGI_FORMAT_R8G8B8A8_UINT*/; + case 40 /*VK_FORMAT_R8G8B8A8_SSCALED*/: return 32 /*DXGI_FORMAT_R8G8B8A8_SINT*/; + case 41 /*VK_FORMAT_R8G8B8A8_UINT*/: return 30 /*DXGI_FORMAT_R8G8B8A8_UINT*/; + case 42 /*VK_FORMAT_R8G8B8A8_SINT*/: return 32 /*DXGI_FORMAT_R8G8B8A8_SINT*/; + case 43 /*VK_FORMAT_R8G8B8A8_SRGB*/: return 29 /*DXGI_FORMAT_R8G8B8A8_UNORM_SRGB*/; + case 44 /*VK_FORMAT_B8G8R8A8_UNORM*/: return 87 /*DXGI_FORMAT_B8G8R8A8_UNORM*/; + case 50 /*VK_FORMAT_B8G8R8A8_SRGB*/: return 91 /*DXGI_FORMAT_B8G8R8A8_UNORM_SRGB*/; + case 64 /*VK_FORMAT_A2B10G10R10_UNORM_PACK32*/: return 24 /*DXGI_FORMAT_R10G10B10A2_UNORM*/; + case 66 /*VK_FORMAT_A2B10G10R10_USCALED_PACK32*/: return 25 /*DXGI_FORMAT_R10G10B10A2_UINT*/; + case 68 /*VK_FORMAT_A2B10G10R10_UINT_PACK32*/: return 25 /*DXGI_FORMAT_R10G10B10A2_UINT*/; + case 70 /*VK_FORMAT_R16_UNORM*/: return 56 /*DXGI_FORMAT_R16_UNORM*/; + case 71 /*VK_FORMAT_R16_SNORM*/: return 58 /*DXGI_FORMAT_R16_SNORM*/; + case 72 /*VK_FORMAT_R16_USCALED*/: return 57 /*DXGI_FORMAT_R16_UINT*/; + case 73 /*VK_FORMAT_R16_SSCALED*/: return 59 /*DXGI_FORMAT_R16_SINT*/; + case 74 /*VK_FORMAT_R16_UINT*/: return 57 /*DXGI_FORMAT_R16_UINT*/; + case 75 /*VK_FORMAT_R16_SINT*/: return 59 /*DXGI_FORMAT_R16_SINT*/; + case 76 /*VK_FORMAT_R16_SFLOAT*/: return 54 /*DXGI_FORMAT_R16_FLOAT*/; + case 77 /*VK_FORMAT_R16G16_UNORM*/: return 35 /*DXGI_FORMAT_R16G16_UNORM*/; + case 78 /*VK_FORMAT_R16G16_SNORM*/: return 37 /*DXGI_FORMAT_R16G16_SNORM*/; + case 79 /*VK_FORMAT_R16G16_USCALED*/: return 36 /*DXGI_FORMAT_R16G16_UINT*/; + case 80 /*VK_FORMAT_R16G16_SSCALED*/: return 38 /*DXGI_FORMAT_R16G16_SINT*/; + case 81 /*VK_FORMAT_R16G16_UINT*/: return 36 /*DXGI_FORMAT_R16G16_UINT*/; + case 82 /*VK_FORMAT_R16G16_SINT*/: return 38 /*DXGI_FORMAT_R16G16_SINT*/; + case 83 /*VK_FORMAT_R16G16_SFLOAT*/: return 34 /*DXGI_FORMAT_R16G16_FLOAT*/; + case 91 /*VK_FORMAT_R16G16B16A16_UNORM*/: return 11 /*DXGI_FORMAT_R16G16B16A16_UNORM*/; + case 92 /*VK_FORMAT_R16G16B16A16_SNORM*/: return 13 /*DXGI_FORMAT_R16G16B16A16_SNORM*/; + case 93 /*VK_FORMAT_R16G16B16A16_USCALED*/: return 12 /*DXGI_FORMAT_R16G16B16A16_UINT*/; + case 94 /*VK_FORMAT_R16G16B16A16_SSCALED*/: return 14 /*DXGI_FORMAT_R16G16B16A16_SINT*/; + case 95 /*VK_FORMAT_R16G16B16A16_UINT*/: return 12 /*DXGI_FORMAT_R16G16B16A16_UINT*/; + case 96 /*VK_FORMAT_R16G16B16A16_SINT*/: return 14 /*DXGI_FORMAT_R16G16B16A16_SINT*/; + case 97 /*VK_FORMAT_R16G16B16A16_SFLOAT*/: return 10 /*DXGI_FORMAT_R16G16B16A16_FLOAT*/; + case 98 /*VK_FORMAT_R32_UINT*/: return 42 /*DXGI_FORMAT_R32_UINT*/; + case 99 /*VK_FORMAT_R32_SINT*/: return 43 /*DXGI_FORMAT_R32_SINT*/; + case 100 /*VK_FORMAT_R32_SFLOAT*/: return 41 /*DXGI_FORMAT_R32_FLOAT*/; + case 101 /*VK_FORMAT_R32G32_UINT*/: return 17 /*DXGI_FORMAT_R32G32_UINT*/; + case 102 /*VK_FORMAT_R32G32_SINT*/: return 18 /*DXGI_FORMAT_R32G32_SINT*/; + case 103 /*VK_FORMAT_R32G32_SFLOAT*/: return 16 /*DXGI_FORMAT_R32G32_FLOAT*/; + case 104 /*VK_FORMAT_R32G32B32_UINT*/: return 7 /*DXGI_FORMAT_R32G32B32_UINT*/; + case 105 /*VK_FORMAT_R32G32B32_SINT*/: return 8 /*DXGI_FORMAT_R32G32B32_SINT*/; + case 106 /*VK_FORMAT_R32G32B32_SFLOAT*/: return 6 /*DXGI_FORMAT_R32G32B32_FLOAT*/; + case 107 /*VK_FORMAT_R32G32B32A32_UINT*/: return 3 /*DXGI_FORMAT_R32G32B32A32_UINT*/; + case 108 /*VK_FORMAT_R32G32B32A32_SINT*/: return 4 /*DXGI_FORMAT_R32G32B32A32_SINT*/; + case 109 /*VK_FORMAT_R32G32B32A32_SFLOAT*/: return 2 /*DXGI_FORMAT_R32G32B32A32_FLOAT*/; + case 122 /*VK_FORMAT_B10G11R11_UFLOAT_PACK32*/: return 26 /*DXGI_FORMAT_R11G11B10_FLOAT*/; + case 123 /*VK_FORMAT_E5B9G9R9_UFLOAT_PACK32*/: return 67 /*DXGI_FORMAT_R9G9B9E5_SHAREDEXP*/; + case 124 /*VK_FORMAT_D16_UNORM*/: return 55 /*DXGI_FORMAT_D16_UNORM*/; + case 126 /*VK_FORMAT_D32_SFLOAT*/: return 40 /*DXGI_FORMAT_D32_FLOAT*/; + case 129 /*VK_FORMAT_D24_UNORM_S8_UINT*/: return 45 /*DXGI_FORMAT_D24_UNORM_S8_UINT*/; + case 131 /*VK_FORMAT_BC1_RGB_UNORM_BLOCK*/: return 71 /*DXGI_FORMAT_BC1_UNORM*/; + case 132 /*VK_FORMAT_BC1_RGB_SRGB_BLOCK*/: return 72 /*DXGI_FORMAT_BC1_UNORM_SRGB*/; + case 133 /*VK_FORMAT_BC1_RGBA_UNORM_BLOCK*/: return 71 /*DXGI_FORMAT_BC1_UNORM*/; + case 134 /*VK_FORMAT_BC1_RGBA_SRGB_BLOCK*/: return 72 /*DXGI_FORMAT_BC1_UNORM_SRGB*/; + case 135 /*VK_FORMAT_BC2_UNORM_BLOCK*/: return 74 /*DXGI_FORMAT_BC2_UNORM*/; + case 136 /*VK_FORMAT_BC2_SRGB_BLOCK*/: return 75 /*DXGI_FORMAT_BC2_UNORM_SRGB*/; + case 137 /*VK_FORMAT_BC3_UNORM_BLOCK*/: return 77 /*DXGI_FORMAT_BC3_UNORM*/; + case 138 /*VK_FORMAT_BC3_SRGB_BLOCK*/: return 78 /*DXGI_FORMAT_BC3_UNORM_SRGB*/; + case 139 /*VK_FORMAT_BC4_UNORM_BLOCK*/: return 80 /*DXGI_FORMAT_BC4_UNORM*/; + case 140 /*VK_FORMAT_BC4_SNORM_BLOCK*/: return 81 /*DXGI_FORMAT_BC4_SNORM*/; + case 141 /*VK_FORMAT_BC5_UNORM_BLOCK*/: return 83 /*DXGI_FORMAT_BC5_UNORM*/; + case 142 /*VK_FORMAT_BC5_SNORM_BLOCK*/: return 84 /*DXGI_FORMAT_BC5_SNORM*/; + case 143 /*VK_FORMAT_BC6H_UFLOAT_BLOCK*/: return 95 /*DXGI_FORMAT_BC6H_UF16*/; + case 144 /*VK_FORMAT_BC6H_SFLOAT_BLOCK*/: return 96 /*DXGI_FORMAT_BC6H_SF16*/; + case 145 /*VK_FORMAT_BC7_UNORM_BLOCK*/: return 98 /*DXGI_FORMAT_BC7_UNORM*/; + case 146 /*VK_FORMAT_BC7_SRGB_BLOCK*/: return 99 /*DXGI_FORMAT_BC7_UNORM_SRGB*/; + default: break; + } + return 0 /*DXGI_FORMAT_UNKNOWN*/; +} + +[[nodiscard]] constexpr uint32_t getVkFormatBitsPerPixel(uint32_t format) { + switch (format) { + case 3 /*VK_FORMAT_B4G4R4A4_UNORM_PACK16*/: return 16; + case 7 /*VK_FORMAT_B5G5R5A1_UNORM_PACK16*/: return 16; + case 9 /*VK_FORMAT_R8_UNORM*/: return 8; + case 10 /*VK_FORMAT_R8_SNORM*/: return 8; + case 11 /*VK_FORMAT_R8_USCALED*/: return 8; + case 12 /*VK_FORMAT_R8_SSCALED*/: return 8; + case 13 /*VK_FORMAT_R8_UINT*/: return 8; + case 14 /*VK_FORMAT_R8_SINT*/: return 8; + case 16 /*VK_FORMAT_R8G8_UNORM*/: return 16; + case 17 /*VK_FORMAT_R8G8_SNORM*/: return 16; + case 18 /*VK_FORMAT_R8G8_USCALED*/: return 16; + case 19 /*VK_FORMAT_R8G8_SSCALED*/: return 16; + case 20 /*VK_FORMAT_R8G8_UINT*/: return 16; + case 21 /*VK_FORMAT_R8G8_SINT*/: return 16; + case 37 /*VK_FORMAT_R8G8B8A8_UNORM*/: return 32; + case 38 /*VK_FORMAT_R8G8B8A8_SNORM*/: return 32; + case 39 /*VK_FORMAT_R8G8B8A8_USCALED*/: return 32; + case 40 /*VK_FORMAT_R8G8B8A8_SSCALED*/: return 32; + case 41 /*VK_FORMAT_R8G8B8A8_UINT*/: return 32; + case 42 /*VK_FORMAT_R8G8B8A8_SINT*/: return 32; + case 43 /*VK_FORMAT_R8G8B8A8_SRGB*/: return 32; + case 44 /*VK_FORMAT_B8G8R8A8_UNORM*/: return 32; + case 50 /*VK_FORMAT_B8G8R8A8_SRGB*/: return 32; + case 64 /*VK_FORMAT_A2B10G10R10_UNORM_PACK32*/: return 32; + case 66 /*VK_FORMAT_A2B10G10R10_USCALED_PACK32*/: return 32; + case 68 /*VK_FORMAT_A2B10G10R10_UINT_PACK32*/: return 32; + case 70 /*VK_FORMAT_R16_UNORM*/: return 16; + case 71 /*VK_FORMAT_R16_SNORM*/: return 16; + case 72 /*VK_FORMAT_R16_USCALED*/: return 16; + case 73 /*VK_FORMAT_R16_SSCALED*/: return 16; + case 74 /*VK_FORMAT_R16_UINT*/: return 16; + case 75 /*VK_FORMAT_R16_SINT*/: return 16; + case 76 /*VK_FORMAT_R16_SFLOAT*/: return 16; + case 77 /*VK_FORMAT_R16G16_UNORM*/: return 32; + case 78 /*VK_FORMAT_R16G16_SNORM*/: return 32; + case 79 /*VK_FORMAT_R16G16_USCALED*/: return 32; + case 80 /*VK_FORMAT_R16G16_SSCALED*/: return 32; + case 81 /*VK_FORMAT_R16G16_UINT*/: return 32; + case 82 /*VK_FORMAT_R16G16_SINT*/: return 32; + case 83 /*VK_FORMAT_R16G16_SFLOAT*/: return 32; + case 91 /*VK_FORMAT_R16G16B16A16_UNORM*/: return 64; + case 92 /*VK_FORMAT_R16G16B16A16_SNORM*/: return 64; + case 93 /*VK_FORMAT_R16G16B16A16_USCALED*/: return 64; + case 94 /*VK_FORMAT_R16G16B16A16_SSCALED*/: return 64; + case 95 /*VK_FORMAT_R16G16B16A16_UINT*/: return 64; + case 96 /*VK_FORMAT_R16G16B16A16_SINT*/: return 64; + case 97 /*VK_FORMAT_R16G16B16A16_SFLOAT*/: return 64; + case 98 /*VK_FORMAT_R32_UINT*/: return 32; + case 99 /*VK_FORMAT_R32_SINT*/: return 32; + case 100 /*VK_FORMAT_R32_SFLOAT*/: return 32; + case 101 /*VK_FORMAT_R32G32_UINT*/: return 64; + case 102 /*VK_FORMAT_R32G32_SINT*/: return 64; + case 103 /*VK_FORMAT_R32G32_SFLOAT*/: return 64; + case 104 /*VK_FORMAT_R32G32B32_UINT*/: return 96; + case 105 /*VK_FORMAT_R32G32B32_SINT*/: return 96; + case 106 /*VK_FORMAT_R32G32B32_SFLOAT*/: return 96; + case 107 /*VK_FORMAT_R32G32B32A32_UINT*/: return 128; + case 108 /*VK_FORMAT_R32G32B32A32_SINT*/: return 128; + case 109 /*VK_FORMAT_R32G32B32A32_SFLOAT*/: return 128; + case 122 /*VK_FORMAT_B10G11R11_UFLOAT_PACK32*/: return 32; + case 123 /*VK_FORMAT_E5B9G9R9_UFLOAT_PACK32*/: return 32; + case 124 /*VK_FORMAT_D16_UNORM*/: return 16; + case 126 /*VK_FORMAT_D32_SFLOAT*/: return 32; + case 129 /*VK_FORMAT_D24_UNORM_S8_UINT*/: return 32; + case 131 /*VK_FORMAT_BC1_RGB_UNORM_BLOCK*/: return 4; + case 132 /*VK_FORMAT_BC1_RGB_SRGB_BLOCK*/: return 4; + case 133 /*VK_FORMAT_BC1_RGBA_UNORM_BLOCK*/: return 4; + case 134 /*VK_FORMAT_BC1_RGBA_SRGB_BLOCK*/: return 4; + case 135 /*VK_FORMAT_BC2_UNORM_BLOCK*/: return 8; + case 136 /*VK_FORMAT_BC2_SRGB_BLOCK*/: return 8; + case 137 /*VK_FORMAT_BC3_UNORM_BLOCK*/: return 8; + case 138 /*VK_FORMAT_BC3_SRGB_BLOCK*/: return 8; + case 139 /*VK_FORMAT_BC4_UNORM_BLOCK*/: return 4; + case 140 /*VK_FORMAT_BC4_SNORM_BLOCK*/: return 4; + case 141 /*VK_FORMAT_BC5_UNORM_BLOCK*/: return 8; + case 142 /*VK_FORMAT_BC5_SNORM_BLOCK*/: return 8; + case 143 /*VK_FORMAT_BC6H_UFLOAT_BLOCK*/: return 8; + case 144 /*VK_FORMAT_BC6H_SFLOAT_BLOCK*/: return 8; + case 145 /*VK_FORMAT_BC7_UNORM_BLOCK*/: return 8; + case 146 /*VK_FORMAT_BC7_SRGB_BLOCK*/: return 8; + default: break; + } + return 0 /*DXGI_FORMAT_UNKNOWN*/; +} + +[[nodiscard]] constexpr bool isVkFormatCompressed(uint32_t format) { + switch (format) { + case 131 /*VK_FORMAT_BC1_RGB_UNORM_BLOCK*/: + case 132 /*VK_FORMAT_BC1_RGB_SRGB_BLOCK*/: + case 133 /*VK_FORMAT_BC1_RGBA_UNORM_BLOCK*/: + case 134 /*VK_FORMAT_BC1_RGBA_SRGB_BLOCK*/: + case 135 /*VK_FORMAT_BC2_UNORM_BLOCK*/: + case 136 /*VK_FORMAT_BC2_SRGB_BLOCK*/: + case 137 /*VK_FORMAT_BC3_UNORM_BLOCK*/: + case 138 /*VK_FORMAT_BC3_SRGB_BLOCK*/: + case 139 /*VK_FORMAT_BC4_UNORM_BLOCK*/: + case 140 /*VK_FORMAT_BC4_SNORM_BLOCK*/: + case 141 /*VK_FORMAT_BC5_UNORM_BLOCK*/: + case 142 /*VK_FORMAT_BC5_SNORM_BLOCK*/: + case 143 /*VK_FORMAT_BC6H_UFLOAT_BLOCK*/: + case 144 /*VK_FORMAT_BC6H_SFLOAT_BLOCK*/: + case 145 /*VK_FORMAT_BC7_UNORM_BLOCK*/: + case 146 /*VK_FORMAT_BC7_SRGB_BLOCK*/: + return true; + default: + break; + } + return false; +} + +} // namespace + +uint32_t PKG::Asset::getBlobIndex(int frame, int face, int mip) const { + if (this->type == Type::UNKNOWN || this->type == Type::BUFFER) { + return this->startBlobIndex; + } + if (this->type == Type::IMAGE_CUBE) { + frame = frame * 6 + face; + } + const auto looseMipCount = this->mipCount - this->tailMipCount; + return (mip >= looseMipCount ? this->endBlobIndex : mip + this->startBlobIndex) + frame * looseMipCount; +} + +std::unique_ptr PKG::open(const std::string& path, const EntryCallback& callback) { + if (!std::filesystem::exists(path)) { + // File does not exist + return nullptr; + } + + auto* pkg = new PKG{path}; + auto packFile = std::unique_ptr(pkg); + + FileStream reader{pkg->fullFilePath}; + reader.seek_in(0); + + if (path.length() >= 7 && string::matches(path.substr(path.length() - 7, path.length()), "_%d%d.pkg")) { + for (int i = 0; true; i++) { + const auto numberedPath = pkg->getTruncatedFilepath() + "_" + string::padNumber(i, 2) + PKG_EXTENSION.data(); + if (!pkg->openNumbered(i, numberedPath, callback)) { + if (i == 0) { + return nullptr; + } + break; + } + } + } else if (!pkg->openNumbered(~0, std::string{pkg->getFilepath()}, callback)) { + // Non-numbered fallback + return nullptr; + } + + return packFile; +} + +bool PKG::openNumbered(uint16_t archiveIndex, const std::string& path, const EntryCallback& callback) { + if (!std::filesystem::exists(path)) { + // File does not exist + return false; + } + + FileStream reader{std::string{path}}; + reader.seek_in(0); + + if (const auto signature = reader.read(); signature != PKG_SIGNATURE) { + // File is not a PKG + return false; + } + + if (const auto version = reader.read(); version != 1) { + // File is an unsupported version + return false; + } + + // Tree offset + reader.seek_in_u(reader.read()); + + this->metadata[archiveIndex] = {}; + auto& [assets, blobs] = this->metadata.at(archiveIndex); + + const auto assetCount = reader.read(); + const auto blobCount = reader.read(); + + assets.reserve(assetCount); + for (int i = 0; i < assetCount; i++) { + auto& [ + nameIndex, + type, + format, + width, + height, + depth, + mipCount, + tailMipCount, + arraySize, + startBlobIndex, + endBlobIndex + ] = assets.emplace_back(); + reader >> nameIndex >> type >> format >> width >> height >> depth >> mipCount >> tailMipCount >> arraySize >> startBlobIndex >> endBlobIndex; + } + + blobs.reserve(blobCount); + for (int i = 0; i < blobCount; i++) { + auto& [ + offset, + compression, + flags, + size, + crc32 + ] = blobs.emplace_back(); + const auto offsetCompressionFlags = reader.read(); + offset = offsetCompressionFlags & 0x000000ffffffffffull; + compression = (offsetCompressionFlags & 0x0000ff0000000000ull) == 0 ? Blob::Compression::NONE : Blob::Compression::GDEFLATE; + flags = offsetCompressionFlags & 0x00ff000000000000ull; + reader >> size >> crc32; + } + + std::vector names; + names.reserve(assetCount); + for (uint32_t i = 0; i < assetCount; i++) { + names.push_back(this->cleanEntryPath(reader.read_string())); + } + + for (uint32_t i = 0; i < assetCount; i++) { + Entry entry = createNewEntry(); + + entry.archiveIndex = archiveIndex; + entry.offset = i; // Not storing the offset! This is asset index! + entry.length = 148; // DDS header size + for (int j = assets[i].startBlobIndex; j <= assets[i].endBlobIndex; j++) { + entry.length += blobs[i].size; + } + + this->entries.emplace(names[assets[i].nameIndex], entry); + + if (callback) { + callback(names[assets[i].nameIndex], entry); + } + } + return true; +} + +std::optional> PKG::readEntry(const std::string& path_) const { + const auto path = this->cleanEntryPath(path_); + const auto entry = this->findEntry(path); + if (!entry) { + return std::nullopt; + } + if (entry->unbaked) { + return readUnbakedEntry(*entry); + } + + // It's baked into the file on disk + FileStream stream{this->getTruncatedFilepath() + "_" + string::padNumber(entry->archiveIndex, 2) + PKG_EXTENSION.data()}; + if (!stream) { + return std::nullopt; + } + + const auto& [assets, blobs] = this->metadata.at(entry->archiveIndex); + const auto& asset = assets[entry->offset]; + + std::vector out; + BufferStream writer{out}; + + // Zeroed DDS header + writer.pad(148); + + // Add blobs + const auto readBlob = [&stream, &blobs, &writer](uint32_t i, uint64_t blobUncompressedSize) { + const auto& blob = blobs[i]; + switch (blob.compression) { + case Blob::Compression::NONE: { + writer << stream.seek_in_u(blob.offset).read_bytes(blob.size); + break; + } + case Blob::Compression::GDEFLATE: { + if (!blob.size) { + return false; + } + const auto blobCompressed = stream.seek_in_u(blob.offset).read_bytes(blob.size); + std::vector blobUncompressed; + blobUncompressed.resize(blobUncompressedSize); + if (!GDeflate::Decompress(reinterpret_cast(blobUncompressed.data()), blobUncompressed.size(), reinterpret_cast(blobCompressed.data()), blobCompressed.size(), 1)) { + return false; + } + writer << blobUncompressed; + break; + } + default: + return false; + } + return true; + }; + + const auto frameCount = asset.arraySize > 0 ? asset.arraySize : 1; + const auto faceCount = asset.type == Asset::Type::IMAGE_CUBE ? 6 : 1; + const auto looseMipCount = asset.mipCount - asset.tailMipCount; + + const auto getMipSize = [&asset](int mip) { + if (::isVkFormatCompressed(asset.format)) { + return (((std::max(asset.width / (1 << mip), 4) + 3) / 4) * 4) * (((std::max(asset.height / (1 << mip), 4) + 3) / 4) * 4) * std::max(asset.depth / (1 << mip), 1) * ::getVkFormatBitsPerPixel(asset.format) / 8; + } + return std::max(asset.width / (1 << mip), 1) * std::max(asset.height / (1 << mip), 1) * std::max(asset.depth / (1 << mip), 1) * ::getVkFormatBitsPerPixel(asset.format) / 8; + }; + const auto getTailMipSize = [&asset, &getMipSize] { + uint32_t count = 0; + for (int mip = 0; mip < asset.tailMipCount; mip++) { + count += getMipSize(asset.mipCount - mip - 1); + } + return count; + }; + + for (int frame = 0; frame < frameCount; frame++) { + for (int face = 0; face < faceCount; face++) { + for (int mip = 0; mip < looseMipCount; mip++) { + if (!readBlob(asset.getBlobIndex(frame, face, mip), getMipSize(mip))) { + return std::nullopt; + } + } + if (asset.tailMipCount > 0 && !readBlob(asset.getBlobIndex(frame, face, looseMipCount), getTailMipSize())) { + return std::nullopt; + } + } + } + out.resize(writer.size()); + + // DDS header + writer + .seek(0) + .write(parser::binary::makeFourCC("DDS ")) + .write(124) + .write(0x1 | 0x2 | 0x4 | 0x1000 | (asset.mipCount > 1 ? 0x20000 : 0x0) | (::isVkFormatCompressed(asset.format) ? 0x80000 : 0x8) | (asset.depth > 1 ? 0x800000 : 0)) + .write(asset.height) + .write(asset.width) + .write(::isVkFormatCompressed(asset.format) ? getMipSize(0) : asset.width * ::getVkFormatBitsPerPixel(asset.format) / 8) + .write(asset.depth) + .write(asset.mipCount) + .pad(11) + .write(32) + .write(0x4) + .write(parser::binary::makeFourCC("DX10")) + .write(0) + .write(0) + .write(0) + .write(0) + .write(0) + .write(0) + .write(0) + .write(0) + .write(0) + .pad() + .write(::mapVkFormatToDXGIFormat(asset.format)) + .write(asset.type == Asset::Type::IMAGE_2D || asset.type == Asset::Type::IMAGE_CUBE ? 3 : asset.type == Asset::Type::IMAGE_3D ? 4 : asset.type == Asset::Type::IMAGE_1D ? 2 : asset.type == Asset::Type::BUFFER ? 1 : 0) + .write(0) + .write(1) + .write(1); + + return out; +} + +std::string PKG::getTruncatedFilestem() const { + std::string stem = this->getFilestem(); + if (stem.length() >= 3) { + stem = stem.substr(0, stem.length() - 3); + } + return stem; +} + +Attribute PKG::getSupportedEntryAttributes() const { + using enum Attribute; + return ARCHIVE_INDEX | LENGTH; +}