From 6fbe900ba92371fa45f7d86d294f7c46ab42c9f5 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Mon, 30 May 2022 02:56:15 +0200 Subject: [PATCH] Initial implementation of compressed VPK rebuilding The 'fs_pack_vpk' allows compressing specific files into a VPK (compressed). This implementation has better compression ratio's then currently available VPK tools with compression (at the cost of speed). The implementation is early, a directory iterator with a files for flags is planned soon to fully rebuild VPK's on-demand. A dedicated standalone program is also planned. The output dir and archive formats should match 1:1 with the original game --- r5dev/tier1/cmd.cpp | 3 +- r5dev/vpklib/packedstore.cpp | 251 +++++++++++++++++++++++++++++------ r5dev/vpklib/packedstore.h | 38 ++++-- r5dev/vstdlib/callback.cpp | 29 +++- r5dev/vstdlib/callback.h | 1 + 5 files changed, 268 insertions(+), 54 deletions(-) diff --git a/r5dev/tier1/cmd.cpp b/r5dev/tier1/cmd.cpp index fd9ddcbc..dcded1e3 100644 --- a/r5dev/tier1/cmd.cpp +++ b/r5dev/tier1/cmd.cpp @@ -147,8 +147,9 @@ void ConCommand::Init(void) #endif // !DEDICATED //------------------------------------------------------------------------- // FILESYSTEM API | - new ConCommand("fs_unpack_vpk", "Unpacks all files from user specified VPK file.", FCVAR_DEVELOPMENTONLY, VPK_Unpack_f, nullptr); new ConCommand("fs_mount_vpk", "Mounts user specified VPK file for FileSystem usage.", FCVAR_DEVELOPMENTONLY, VPK_Mount_f, nullptr); + new ConCommand("fs_pack_vpk", "Packs all specified files into a VPK file.", FCVAR_DEVELOPMENTONLY, VPK_Pack_f, nullptr); + new ConCommand("fs_unpack_vpk", "Unpacks all files from user specified VPK file.", FCVAR_DEVELOPMENTONLY, VPK_Unpack_f, nullptr); //------------------------------------------------------------------------- // RTECH API | new ConCommand("rtech_strtoguid", "Calculates the GUID from input data.", FCVAR_DEVELOPMENTONLY, RTech_StringToGUID_f, nullptr); diff --git a/r5dev/vpklib/packedstore.cpp b/r5dev/vpklib/packedstore.cpp index 52439160..c72d3961 100644 --- a/r5dev/vpklib/packedstore.cpp +++ b/r5dev/vpklib/packedstore.cpp @@ -19,7 +19,10 @@ void CPackedStore::InitLzCompParams(void) { /*| PARAMETERS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||*/ - m_lzCompParams.m_dict_size_log2 = RVPK_DICT_SIZE; + m_lzCompParams.m_dict_size_log2 = RVPK_DICT_SIZE; + m_lzCompParams.m_level = lzham_compress_level::LZHAM_COMP_LEVEL_UBER; + m_lzCompParams.m_compress_flags = lzham_compress_flags::LZHAM_COMP_FLAG_DETERMINISTIC_PARSING | lzham_compress_flags::LZHAM_COMP_FLAG_TRADEOFF_DECOMPRESSION_RATE_FOR_COMP_RATIO; + m_lzCompParams.m_max_helper_threads = -1; } //----------------------------------------------------------------------------- @@ -29,7 +32,7 @@ void CPackedStore::InitLzDecompParams(void) { /*| PARAMETERS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||*/ m_lzDecompParams.m_dict_size_log2 = RVPK_DICT_SIZE; - m_lzDecompParams.m_decompress_flags = LZHAM_DECOMP_FLAG_OUTPUT_UNBUFFERED | LZHAM_DECOMP_FLAG_COMPUTE_CRC32; + m_lzDecompParams.m_decompress_flags = lzham_decompress_flags::LZHAM_DECOMP_FLAG_OUTPUT_UNBUFFERED | lzham_decompress_flags::LZHAM_DECOMP_FLAG_COMPUTE_CRC32; m_lzDecompParams.m_struct_size = sizeof(lzham_decompress_params); } @@ -88,19 +91,19 @@ VPKDir_t CPackedStore::GetPackDirFile(string svPackDirFile) //----------------------------------------------------------------------------- // Purpose: obtains and returns the entry block to the vector //----------------------------------------------------------------------------- -vector CPackedStore::GetEntryBlocks(CIOStream* reader) +vector CPackedStore::GetEntryBlocks(CIOStream* pReader) { /*| ENTRYBLOCKS |||||||||||||||||||||||||||||||||||||||||||||||||||||||||*/ string svName, svPath, svExtension; vector vBlocks; - while (!(svExtension = reader->readString()).empty()) + while (!(svExtension = pReader->ReadString()).empty()) { - while (!(svPath = reader->readString()).empty()) + while (!(svPath = pReader->ReadString()).empty()) { - while (!(svName = reader->readString()).empty()) + while (!(svName = pReader->ReadString()).empty()) { string svFilePath = FormatBlockPath(svName, svPath, svExtension); - vBlocks.push_back(VPKEntryBlock_t(reader, svFilePath)); + vBlocks.push_back(VPKEntryBlock_t(pReader, svFilePath)); } } } @@ -190,18 +193,65 @@ void CPackedStore::ValidateCRC32PostDecomp(string svDirAsset) m_vHashBuffer.clear(); } +void CPackedStore::PackAll(string svDirIn, string svPathOut) +{ + vector uData; // Raw file data + vector vEntryBlocks; + + ifstream iData(svDirIn, fstream::binary); + + iData.seekg(0, fstream::end); + uData.resize(iData.tellg()); + iData.seekg(0, fstream::beg); + iData.read(reinterpret_cast(uData.data()), uData.size()); + + ofstream oBlock(svDirIn, fstream::binary); + vEntryBlocks.push_back(VPKEntryBlock_t(uData, oBlock.tellp(), 0, 0x101, 0, svDirIn)); + + for (size_t i = 0; i < vEntryBlocks.size(); i++) + { + for (VPKEntryDescriptor_t& entry : vEntryBlocks.at(i).m_vvEntries) + { + uint8_t* pSrc = new uint8_t[entry.m_nUncompressedSize]; + uint8_t* pDest = new uint8_t[entry.m_nUncompressedSize]; + + iData.seekg(entry.m_nArchiveOffset); // Seek to entry offset in archive. + iData.read(reinterpret_cast(pSrc), entry.m_nUncompressedSize); // Read compressed data from archive. + + m_lzCompStatus = lzham_compress_memory(&m_lzCompParams, pDest, &entry.m_nCompressedSize, pSrc, entry.m_nUncompressedSize, &m_nAdler32_Internal, &m_nCrc32_Internal); + + if (m_lzCompStatus != lzham_compress_status_t::LZHAM_COMP_STATUS_SUCCESS) + { + Error(eDLL_T::FS, "Error: failed compression for an entry within block '%s' for archive '%d'\n", vEntryBlocks.at(i).m_svBlockPath.c_str(), i); + Error(eDLL_T::FS, "'lzham::lzham_lib_compress_memory' returned with status '%d'.\n", m_lzCompStatus); + } + + entry.m_nArchiveOffset = oBlock.tellp(); + entry.m_bIsCompressed = entry.m_nCompressedSize != entry.m_nUncompressedSize; + + oBlock.write(reinterpret_cast(pDest), entry.m_nCompressedSize); + + delete[] pSrc; + delete[] pDest; + } + } + + VPKDir_t vDir = VPKDir_t(); + vDir.Build("englishclient_mp_rr_test.bsp.pak000_dir.vpk", vEntryBlocks); // [!!! <> !!!] +} + //----------------------------------------------------------------------------- // Purpose: extracts all files from specified vpk file //----------------------------------------------------------------------------- -void CPackedStore::UnpackAll(VPKDir_t vpk_dir, string svPathOut) +void CPackedStore::UnpackAll(VPKDir_t vpkDir, string svPathOut) { - for (int i = 0; i < vpk_dir.m_vsvArchives.size(); i++) + for (int i = 0; i < vpkDir.m_vsvArchives.size(); i++) { - fs::path fspVpkPath(vpk_dir.m_svDirPath); - string svPath = fspVpkPath.parent_path().u8string() + "\\" + vpk_dir.m_vsvArchives[i]; + fs::path fspVpkPath(vpkDir.m_svDirPath); + string svPath = fspVpkPath.parent_path().u8string() + "\\" + vpkDir.m_vsvArchives[i]; ifstream packChunkStream(svPath, std::ios_base::binary); // Create stream to read from each archive. - for ( VPKEntryBlock_t block : vpk_dir.m_vvEntryBlocks) + for ( VPKEntryBlock_t block : vpkDir.m_vvEntryBlocks) { // Escape if block archive index is not part of the extracting archive chunk index. if (block.m_iArchiveIndex != i) { goto escape; } @@ -215,7 +265,7 @@ void CPackedStore::UnpackAll(VPKDir_t vpk_dir, string svPathOut) Error(eDLL_T::FS, "Error: unable to access file '%s'!\n", svFilePath.c_str()); } outFileStream.clear(); // Make sure file is empty before writing. - for (VPKEntryDescr_t entry : block.m_vvEntries) + for (VPKEntryDescriptor_t entry : block.m_vvEntries) { char* pCompressedData = new char[entry.m_nCompressedSize]; memset(pCompressedData, 0, entry.m_nCompressedSize); // Compressed region. @@ -226,7 +276,9 @@ void CPackedStore::UnpackAll(VPKDir_t vpk_dir, string svPathOut) if (entry.m_bIsCompressed) { lzham_uint8* pLzOutputBuf = new lzham_uint8[entry.m_nUncompressedSize]; - m_lzDecompStatus = lzham_decompress_memory(&m_lzDecompParams, pLzOutputBuf, (size_t*)&entry.m_nUncompressedSize, (lzham_uint8*)pCompressedData, entry.m_nCompressedSize, &m_nAdler32_Internal, &m_nCrc32_Internal); + m_lzDecompStatus = lzham_decompress_memory(&m_lzDecompParams, pLzOutputBuf, + (size_t*)&entry.m_nUncompressedSize, (lzham_uint8*)pCompressedData, + entry.m_nCompressedSize, &m_nAdler32_Internal, &m_nCrc32_Internal); if (fs_packedstore_entryblock_stats->GetBool()) { @@ -253,20 +305,21 @@ void CPackedStore::UnpackAll(VPKDir_t vpk_dir, string svPathOut) if (m_lzDecompStatus != lzham_decompress_status_t::LZHAM_DECOMP_STATUS_SUCCESS) { Error(eDLL_T::FS, "Error: failed decompression for an entry within block '%s' in archive '%d'!\n", block.m_svBlockPath.c_str(), i); - Error(eDLL_T::FS, "'lzham_decompress_memory_func' returned with status '%d'.\n", m_lzDecompStatus); + Error(eDLL_T::FS, "'lzham::lzham_lib_decompress_memory' returned with status '%d'.\n", m_lzDecompStatus); } else { // If successfully decompressed, write to file. outFileStream.write((char*)pLzOutputBuf, entry.m_nUncompressedSize); - delete[] pLzOutputBuf, pCompressedData; } + delete[] pLzOutputBuf; } else { // If not compressed, write raw data into output file. outFileStream.write(pCompressedData, entry.m_nUncompressedSize); } + delete[] pCompressedData; } outFileStream.close(); if (m_nEntryCount == block.m_vvEntries.size()) // Only validate after last entry in block had been written. @@ -292,57 +345,89 @@ VPKEntryBlock_t::VPKEntryBlock_t(CIOStream* reader, string svPath) std::replace(svPath.begin(), svPath.end(), '/', '\\'); // Flip forward slashes in filepath to windows-style backslash. this->m_svBlockPath = svPath; // Set path of block. - reader->read(this->m_nCrc32); // - reader->read(this->m_nPreloadBytes); // - reader->read(this->m_iArchiveIndex); // + reader->Read(this->m_nCrc32); // + reader->Read(this->m_nPreloadBytes); // + reader->Read(this->m_iArchiveIndex); // do // Loop through all entries in the block and push them to the vector. { - VPKEntryDescr_t entry(reader); + VPKEntryDescriptor_t entry(reader); this->m_vvEntries.push_back(entry); - } while (reader->readR() != 65535); + } while (reader->Read() != 0xFFFF); +} + +VPKEntryBlock_t::VPKEntryBlock_t(const vector &vData, int64_t nOffset, uint16_t nArchiveIndex, uint32_t nEntryFlags, uint16_t nTextureFlags, string svBlockPath) +{ + m_nCrc32 = crc32::update(NULL, vData.data(), vData.size()); + m_nPreloadBytes = 0; + m_iArchiveIndex = nArchiveIndex; + m_svBlockPath = svBlockPath; + + int nEntryCount = (vData.size() + RVPK_MAX_BLOCK - 1) / RVPK_MAX_BLOCK; + m_vvEntries = vector(nEntryCount); + + int64_t nDataSize = vData.size(); + int64_t nCurrentOffset = nOffset; + for (int i = 0; i < nEntryCount; i++) + { + int64_t nSize = min(RVPK_MAX_BLOCK, nDataSize); + nDataSize -= nSize; + m_vvEntries[i] = VPKEntryDescriptor_t(nEntryFlags, nTextureFlags, nCurrentOffset, nSize, nSize); + nCurrentOffset += nSize; + } +} + +VPKEntryDescriptor_t::VPKEntryDescriptor_t(uint32_t nEntryFlags, uint16_t nTextureFlags, uint64_t nArchiveOffset, uint64_t nCompressedSize, uint64_t nUncompressedSize) +{ + m_nEntryFlags = nEntryFlags; + m_nTextureFlags = nTextureFlags; + m_nArchiveOffset = nArchiveOffset; + + m_nCompressedSize = nCompressedSize; + m_nUncompressedSize = nUncompressedSize; } //----------------------------------------------------------------------------- -// Purpose: 'vpk_entry_h' constructor +// Purpose: 'VPKDir_t' constructor +// Input : *pReader - //----------------------------------------------------------------------------- -VPKEntryDescr_t::VPKEntryDescr_t(CIOStream* reader) +VPKEntryDescriptor_t::VPKEntryDescriptor_t(CIOStream* pReader) { - reader->read(this->m_nEntryFlags); // - reader->read(this->m_nTextureFlags); // - reader->read(this->m_nArchiveOffset); // - reader->read(this->m_nCompressedSize); // - reader->read(this->m_nUncompressedSize); // + pReader->Read(this->m_nEntryFlags); // + pReader->Read(this->m_nTextureFlags); // + pReader->Read(this->m_nArchiveOffset); // + pReader->Read(this->m_nCompressedSize); // + pReader->Read(this->m_nUncompressedSize); // this->m_bIsCompressed = (this->m_nCompressedSize != this->m_nUncompressedSize); } //----------------------------------------------------------------------------- -// Purpose: 'vpk_dir_h' constructor +// Purpose: 'VPKDir_t' file constructor //----------------------------------------------------------------------------- -VPKDir_t::VPKDir_t(string svPath) +VPKDir_t::VPKDir_t(const string& svPath) { CIOStream reader; - reader.open(svPath, eStreamFileMode::READ); - reader.read(this->m_nFileMagic); + reader.Open(svPath, eStreamFileMode::READ); + reader.Read(this->m_vHeader.m_nFileMagic); - if (this->m_nFileMagic != RVPK_DIR_MAGIC) + if (this->m_vHeader.m_nFileMagic != RVPK_DIR_MAGIC) { Error(eDLL_T::FS, "Error: vpk_dir file '%s' has invalid magic!\n", svPath.c_str()); - return; + //return; } - reader.read(this->m_nMajorVersion); // - reader.read(this->m_nMinorVersion); // - reader.read(this->m_nTreeSize); // - reader.read(this->m_nFileDataSize); // + reader.Read(this->m_vHeader.m_nMajorVersion); // + reader.Read(this->m_vHeader.m_nMinorVersion); // + reader.Read(this->m_vHeader.m_nTreeSize); // + reader.Read(this->m_nFileDataSize); // DevMsg(eDLL_T::FS, "______________________________________________________________\n"); DevMsg(eDLL_T::FS, "] HEADER_DETAILS ---------------------------------------------\n"); - DevMsg(eDLL_T::FS, "] File Magic : '%lu'\n", this->m_nFileMagic); - DevMsg(eDLL_T::FS, "] Major Version : '%hu'\n", this->m_nMajorVersion); - DevMsg(eDLL_T::FS, "] Minor Version : '%hu'\n", this->m_nMinorVersion); - DevMsg(eDLL_T::FS, "] Tree Size : '%lu'\n", this->m_nTreeSize); + DevMsg(eDLL_T::FS, "] File Magic : '%lu'\n", this->m_vHeader.m_nFileMagic); + DevMsg(eDLL_T::FS, "] Major Version : '%hu'\n", this->m_vHeader.m_nMajorVersion); + DevMsg(eDLL_T::FS, "] Minor Version : '%hu'\n", this->m_vHeader.m_nMinorVersion); + DevMsg(eDLL_T::FS, "] Tree Size : '%lu'\n", this->m_vHeader.m_nTreeSize); DevMsg(eDLL_T::FS, "] File Data Size : '%lu'\n", this->m_nFileDataSize); this->m_vvEntryBlocks = g_pPackedStore->GetEntryBlocks(&reader); @@ -366,5 +451,87 @@ VPKDir_t::VPKDir_t(string svPath) this->m_vsvArchives.push_back(svArchivePath); } } + +//----------------------------------------------------------------------------- +// Purpose: builds the VPKDir file +// Input : &svFileName - +// &vEntryBlocks - +//----------------------------------------------------------------------------- +void VPKDir_t::Build(const string& svFileName, const vector& vEntryBlocks) +{ + CIOStream writer(svFileName, eStreamFileMode::WRITE); + auto vMap = std::map>>(); + + writer.Write(this->m_vHeader.m_nFileMagic); + writer.Write(this->m_vHeader.m_nMajorVersion); + writer.Write(this->m_vHeader.m_nMinorVersion); + writer.Write(this->m_vHeader.m_nTreeSize); + writer.Write(this->m_vHeader.m_nTreeSize); + + for (VPKEntryBlock_t vBlock : vEntryBlocks) + { + string svExtension = GetExtension(vBlock.m_svBlockPath); + string svFileName = GetFileName(vBlock.m_svBlockPath); + string svFilePath = RemoveFileName(vBlock.m_svBlockPath); + + if (!vMap.count(svExtension)) + { + vMap.insert({ svExtension, std::map>() }); + } + if (!vMap[svExtension].count(svFilePath)) + { + vMap[svExtension].insert({ svFilePath, std::list() }); + } + vMap[svExtension][svFilePath].push_back(vBlock); + } + + for (auto& iKeyValue : vMap) + { + writer.WriteString(iKeyValue.first); + for (auto& jKeyValue : iKeyValue.second) + { + writer.WriteString(jKeyValue.first); + for (auto& eKeyValue : jKeyValue.second) + { + writer.WriteString(GetFileName(eKeyValue.m_svBlockPath, true)); + {/*Write entry block*/ + writer.Write(eKeyValue.m_nCrc32); + writer.Write(eKeyValue.m_nPreloadBytes); + writer.Write(eKeyValue.m_iArchiveIndex); + + for (size_t i = 0; i < eKeyValue.m_vvEntries.size(); i++) + { + {/*Write entry descriptor*/ + writer.Write(eKeyValue.m_vvEntries[i].m_nEntryFlags); + writer.Write(eKeyValue.m_vvEntries[i].m_nTextureFlags); + writer.Write(eKeyValue.m_vvEntries[i].m_nArchiveOffset); + writer.Write(eKeyValue.m_vvEntries[i].m_nCompressedSize); + writer.Write(eKeyValue.m_vvEntries[i].m_nUncompressedSize); + } + + if (i != (eKeyValue.m_vvEntries.size() - 1)) + { + const ushort s = 0; + writer.Write(s); + } + else + { + const ushort s = UINT16_MAX; + writer.Write(s); + } + } + } + writer.Write('\0'); + } + writer.Write('\0'); + } + writer.Write('\0'); + } + m_vHeader.m_nTreeSize = static_cast(writer.GetPosition() - sizeof(VPKHeader_t)); + + writer.SetPosition(offsetof(VPKDir_t, m_vHeader.m_nTreeSize)); + writer.Write(this->m_vHeader.m_nTreeSize); + writer.Write(0); +} /////////////////////////////////////////////////////////////////////////////// CPackedStore* g_pPackedStore = new CPackedStore(); diff --git a/r5dev/vpklib/packedstore.h b/r5dev/vpklib/packedstore.h index 82313f2c..6d9db2c4 100644 --- a/r5dev/vpklib/packedstore.h +++ b/r5dev/vpklib/packedstore.h @@ -2,10 +2,15 @@ #include "public/include/binstream.h" #include "thirdparty/lzham/include/lzham.h" +constexpr unsigned int RVPK_DIR_MAGIC = 'UŠ4'; +constexpr unsigned int RVPK_MAJOR_VER = 2; +constexpr unsigned int RVPK_MINOR_VER = 3; + constexpr unsigned int LIBRARY_PACKS = 2; constexpr unsigned int LANGUAGE_PACKS = 11; constexpr unsigned int RVPK_DICT_SIZE = 20; -constexpr unsigned int RVPK_DIR_MAGIC = 'UŠ4'; +constexpr int RVPK_MAX_BLOCK = 1024 * 1024; +constexpr int DECOMP_MAX_BUF = 2024 * 2024; const std::string DIR_LIBRARY_PREFIX[LIBRARY_PACKS] = { "server", "client" }; const std::string DIR_LOCALE_PREFIX[LANGUAGE_PACKS] = { "english", "french", "german", "italian", "japanese", "korean", "polish", "portuguese", "russian", "spanish", "tchinese" }; @@ -31,7 +36,7 @@ struct VPKData_t }; #pragma pack(pop) -struct VPKEntryDescr_t +struct VPKEntryDescriptor_t { uint32_t m_nEntryFlags {}; // Entry flags. uint16_t m_nTextureFlags {}; // Texture flags (only used if the entry is a vtf). @@ -40,33 +45,45 @@ struct VPKEntryDescr_t uint64_t m_nUncompressedSize{}; // Uncompressed size of entry. bool m_bIsCompressed = false; - VPKEntryDescr_t(CIOStream* reader); + VPKEntryDescriptor_t(){}; + VPKEntryDescriptor_t(CIOStream* reader); + VPKEntryDescriptor_t(uint32_t nEntryFlags, uint16_t nTextureFlags, uint64_t nArchiveOffset, uint64_t nCompressedSize, uint64_t nUncompressedSize); }; struct VPKEntryBlock_t { - uint32_t m_nCrc32 {}; // Crc32 for the uncompressed block. - uint16_t m_nPreloadBytes{}; // Preload bytes. - uint16_t m_iArchiveIndex{}; // Index of the archive that contains this block. - vector m_vvEntries {}; // Vector of all the entries of a given block (entries have a size limit of 1 MiB, so anything over is split into separate entries within the same block). - string m_svBlockPath {}; // Path to block within vpk. + uint32_t m_nCrc32 {}; // Crc32 for the uncompressed block. + uint16_t m_nPreloadBytes{}; // Preload bytes. + uint16_t m_iArchiveIndex{}; // Index of the archive that contains this block. + vector m_vvEntries {}; // Vector of all the entries of a given block (entries have a size limit of 1 MiB, so anything over is split into separate entries within the same block). + string m_svBlockPath {}; // Path to block within vpk. VPKEntryBlock_t(CIOStream* reader, string path); + VPKEntryBlock_t(const vector& vData, int64_t nOffset, uint16_t nArchiveIndex, uint32_t nEntryFlags, uint16_t nTextureFlags, string svBlockPath); }; -struct VPKDir_t +struct VPKHeader_t { uint32_t m_nFileMagic {}; // File magic. uint16_t m_nMajorVersion{}; // Vpk major version. uint16_t m_nMinorVersion{}; // Vpk minor version. uint32_t m_nTreeSize {}; // Directory tree size. + uint32_t m_nPadding {}; // Padding to align the header. +}; + +struct VPKDir_t +{ + VPKHeader_t m_vHeader {}; // Dir header. uint32_t m_nFileDataSize{}; // File data section size. vector m_vvEntryBlocks{}; // Vector of entry blocks. uint16_t m_iArchiveCount{}; // Highest archive index (archive count-1). vector m_vsvArchives {}; // Vector of archive file names. string m_svDirPath {}; // Path to vpk_dir file. - VPKDir_t(string path); + VPKDir_t() { m_vHeader.m_nFileMagic = RVPK_DIR_MAGIC; m_vHeader.m_nMajorVersion = RVPK_MAJOR_VER; m_vHeader.m_nMinorVersion = RVPK_MINOR_VER; }; + VPKDir_t(const string& svPath); + + void Build(const string& svFileName, const vector& vEntryBlocks); }; class CPackedStore @@ -90,6 +107,7 @@ public: vector GetEntryBlocks(CIOStream* reader); string FormatBlockPath(string svName, string svPath, string svExtension); string StripLocalePrefix(string svPackDirFile); + void PackAll(string svDirIn, string svPathOut = ""); void UnpackAll(VPKDir_t vpk, string svPathOut = ""); void ValidateAdler32PostDecomp(string svDirAsset); void ValidateCRC32PostDecomp(string svDirAsset); diff --git a/r5dev/vstdlib/callback.cpp b/r5dev/vstdlib/callback.cpp index ca8793db..b36388ba 100644 --- a/r5dev/vstdlib/callback.cpp +++ b/r5dev/vstdlib/callback.cpp @@ -611,7 +611,34 @@ void RTech_Decompress_f(const CCommand& args) /* ===================== -VPK_Decompress_f +VPK_Pack_f + + Compresses new VPK files and + dumps the output to '\vpk'. +===================== +*/ +void VPK_Pack_f(const CCommand& args) +{ + if (args.ArgC() < 2) + { + return; + } + string szPathOut = "platform\\vpk"; + std::chrono::milliseconds msStart = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + //VPKDir_t vpk = g_pPackedStore->GetPackDirFile(args.Arg(1)); + g_pPackedStore->InitLzCompParams(); + + std::thread th([&] { g_pPackedStore->PackAll(args.Arg(1), szPathOut); }); + th.join(); + + std::chrono::milliseconds msEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + float duration = msEnd.count() - msStart.count(); +} + +/* +===================== +VPK_Unpack_f Decompresses input VPK files and dumps the output to '\vpk'. diff --git a/r5dev/vstdlib/callback.h b/r5dev/vstdlib/callback.h index 965e6a0b..32c34d0c 100644 --- a/r5dev/vstdlib/callback.h +++ b/r5dev/vstdlib/callback.h @@ -29,6 +29,7 @@ void Pak_RequestLoad_f(const CCommand& args); void Pak_Swap_f(const CCommand& args); void RTech_StringToGUID_f(const CCommand& args); void RTech_Decompress_f(const CCommand& args); +void VPK_Pack_f(const CCommand& args); void VPK_Unpack_f(const CCommand& args); void VPK_Mount_f(const CCommand& args); void NET_SetKey_f(const CCommand& args);