├── .gitignore ├── clear.bat ├── quote ├── dll │ ├── zlib.dll │ └── libzstd.dll ├── lib │ ├── zlib.lib │ └── libzstd.lib └── header │ ├── zconf.h │ ├── zlib.h │ └── zstd.h ├── huffman ├── huffmanNode.h ├── CMakeLists.txt ├── huffmanDecoder.h ├── huffmanEncoder.h ├── huffmanDecoder.cpp └── huffmanEncoder.cpp ├── main.h ├── packFunc ├── CMakeLists.txt ├── packFunc.h ├── enc.hpp ├── unpack.cpp └── topack.cpp ├── Readme.md ├── CMakeLists.txt └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs/ 3 | .vscode/ -------------------------------------------------------------------------------- /clear.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | del /q /s build&&rmdir /q /s build -------------------------------------------------------------------------------- /quote/dll/zlib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/dll/zlib.dll -------------------------------------------------------------------------------- /quote/lib/zlib.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/lib/zlib.lib -------------------------------------------------------------------------------- /quote/header/zconf.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/header/zconf.h -------------------------------------------------------------------------------- /quote/header/zlib.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/header/zlib.h -------------------------------------------------------------------------------- /quote/header/zstd.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/header/zstd.h -------------------------------------------------------------------------------- /huffman/huffmanNode.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/huffman/huffmanNode.h -------------------------------------------------------------------------------- /quote/dll/libzstd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/dll/libzstd.dll -------------------------------------------------------------------------------- /quote/lib/libzstd.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akiWagashi/GIGA_NeXAS/HEAD/quote/lib/libzstd.lib -------------------------------------------------------------------------------- /huffman/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(. HUFFMAN_DIR) 2 | 3 | add_library(HUFFMAN_LIB ${HUFFMAN_DIR}) -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | #ifndef NEXAS_MAIN 2 | #define NEXAS_MAIN 3 | 4 | #include 5 | #include 6 | 7 | #include "packFunc\packFunc.h" 8 | 9 | #endif //NEXAS_MAIN 10 | -------------------------------------------------------------------------------- /packFunc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(. PACKFUNC_DIR) 2 | 3 | add_library(PACKFUNC_LIB ${PACKFUNC_DIR}) 4 | 5 | target_include_directories(PACKFUNC_LIB PUBLIC ${CMAKE_SOURCE_DIR}) -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## GIGA_NEXAS 2 | 戏画引擎解/封包 3 | 解包:ToolName -x [CP_ACP|CP_UTF8] 4 | 封包:ToolName -c [CP_ACP|CP_UTF8] 5 | 老版本采用zlib,新版本采用zstd 6 | 不过戏画具体从什么时候开始换的压缩方式我也不太清楚orz 7 | 8 | ### 参考 9 | https://github.com/pkuislm/NexasPackEdit 10 | https://github.com/Yggdrasill-Moe/Niflheim/blob/master/NeXAS/pac_unpack/Huffman_dec.h 11 | 优化 by https://github.com/crskycode -------------------------------------------------------------------------------- /packFunc/packFunc.h: -------------------------------------------------------------------------------- 1 | #ifndef NEXAS_PACKFUNC 2 | #define NEXAS_PACKFUNC 3 | 4 | #include 5 | #include 6 | 7 | struct PackageEntry 8 | { 9 | char Name[0x40]; 10 | uint32_t Position; 11 | uint32_t OriginalSize; 12 | uint32_t CompressedSize; 13 | }; 14 | 15 | static_assert(sizeof(PackageEntry) == 0x4C, "The size of PackageEntry must be 4C"); 16 | 17 | bool CreatePackage(const std::string& pacPath, const std::string& dirPath, int compressionMethod,int codePage); 18 | bool CreatePackageMT(const std::string& pacPath, const std::string& dirPath, int compressionMethod,int codePage); 19 | bool ExtractPackage(const std::string& pacPath, const std::string& dirPath,int codePage); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /huffman/huffmanDecoder.h: -------------------------------------------------------------------------------- 1 | #ifndef NEXAS_HUFFMAN_DECODER_H 2 | #define NEXAS_HUFFMAN_DECODER_H 3 | 4 | #include "huffmanNode.h" 5 | 6 | class HuffmanDecoder 7 | { 8 | private: 9 | HuffmanDecoder() = delete; 10 | 11 | void ParseBitStreamToHuffmanTree(); 12 | 13 | uint32_t GetBits(uint32_t getCount); 14 | 15 | public: 16 | HuffmanDecoder(uint8_t const * const buffer, uint32_t bufferSize) : _buffer(buffer), _bufferSize(bufferSize){} 17 | 18 | ~HuffmanDecoder() = default; 19 | 20 | uint32_t Decode(uint8_t* const decodeBuffer, uint32_t decodeBufferSize); 21 | 22 | private: 23 | uint8_t const * const _buffer = nullptr; 24 | uint32_t _bufferSize = 0; 25 | uint32_t _bitCount = 0; 26 | uint32_t _byteCount = 0; 27 | uint32_t _curValue = 0; 28 | std::shared_ptr _root = nullptr; 29 | }; 30 | 31 | 32 | #endif // NEXAS_HUFFMAN_DECODER_H -------------------------------------------------------------------------------- /huffman/huffmanEncoder.h: -------------------------------------------------------------------------------- 1 | #ifndef NEXAS_HUFFMAN_ENCODER_H 2 | #define NEXAS_HUFFMAN_ENCODER_H 3 | 4 | #include "huffmanNode.h" 5 | 6 | #include 7 | 8 | class HuffmanEncoder 9 | { 10 | private: 11 | void SetBits(std::vector& buffer,uint32_t setBit,uint32_t setValue); 12 | 13 | void ConstructionPath(std::shared_ptr& node,const std::vector& nodePath,const uint32_t branch); 14 | 15 | void ParseHuffmanTreeToBitStream(std::vector& streamBuffer,const std::shared_ptr& node); 16 | 17 | public: 18 | HuffmanEncoder() = default; 19 | 20 | ~HuffmanEncoder() = default; 21 | 22 | std::vector Encode(uint8_t* const buffer,uint32_t bufferSize); 23 | 24 | private: 25 | std::shared_ptr _root = nullptr; 26 | uint32_t _bitCount = 8; 27 | uint32_t _curValue = 0; 28 | }; 29 | 30 | #endif // NEXAS_HUFFMAN_ENCODER_H -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR) 2 | 3 | project(GIGA_NEXAS LANGUAGES CXX) 4 | 5 | add_subdirectory(huffman) 6 | 7 | add_subdirectory(packFunc) 8 | 9 | list(APPEND EXTRA_LIB HUFFMAN_LIB) 10 | 11 | list(APPEND EXTRA_LIB PACKFUNC_LIB) 12 | 13 | include_directories("quote/header/") 14 | 15 | link_directories("quote/lib/") 16 | 17 | list(APPEND EXTRA_DLL "zlib.dll" "libzstd.dll") 18 | 19 | add_executable(GIGA_NeXAS main.cpp) 20 | 21 | target_link_libraries(GIGA_NeXAS PUBLIC ${EXTRA_LIB} PUBLIC ${EXTRA_DLL}) 22 | 23 | add_custom_command(TARGET GIGA_NeXAS POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/Readme.md" $) 24 | 25 | file(GLOB EXTRA_DLL_FILES "quote/dll/*.dll") 26 | 27 | foreach(singleDll ${EXTRA_DLL_FILES}) 28 | add_custom_command(TARGET GIGA_NeXAS POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${singleDll} $) 29 | endforeach(singleDll) 30 | 31 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "packFunc/packFunc.h" 7 | 8 | bool IsDirectoryPath(const std::string& path) 9 | { 10 | struct stat s; 11 | stat(path.c_str(), &s); 12 | return (s.st_mode & S_IFDIR) != 0; 13 | } 14 | 15 | int GetCompressionMethod(const char* const name) 16 | { 17 | if (stricmp(name, "zlib") == 0) 18 | return 4; 19 | else if (stricmp(name, "zstd") == 0) 20 | return 7; 21 | return 0; 22 | } 23 | 24 | inline int GetCodePage(const char* const codePage) 25 | { 26 | if(stricmp(codePage,"CP_UTF8")==0) return CP_UTF8; 27 | return CP_ACP; 28 | } 29 | 30 | int main(int argc, char** argv) 31 | { 32 | if (argc < 4) 33 | { 34 | printf("NeXAS Pack Tool\n"); 35 | printf("Usage:\n"); 36 | printf(" Create Package : Tool -c [CP_ACP|CP_UTF8]\n"); 37 | printf(" Extract Package : Tool -x [CP_ACP|CP_UTF8]\n"); 38 | printf(" Default CodePage is CP_ACP (Encoding : shift_jis)\n"); 39 | return 1; 40 | } 41 | 42 | // Replaces with std::string_view in C++17 43 | std::string cmd(argv[1]); 44 | 45 | int codePage = CP_ACP; 46 | 47 | if (cmd == "-c") 48 | { 49 | if (argc < 5) 50 | { 51 | printf("ERROR: Required 4 arguments."); 52 | return 1; 53 | } 54 | 55 | std::string pacPath(argv[3]); 56 | std::string dirPath(argv[4]); 57 | 58 | if (!IsDirectoryPath(dirPath)) 59 | { 60 | printf("ERROR: Path is not a directory."); 61 | return 1; 62 | } 63 | 64 | int compressionMethod = GetCompressionMethod(argv[2]); 65 | 66 | if(argc>=6) codePage = GetCodePage(argv[5]); 67 | 68 | CreatePackageMT(pacPath, dirPath, compressionMethod,codePage); 69 | } 70 | else if (cmd == "-x") 71 | { 72 | std::string pacPath(argv[2]); 73 | std::string dirPath(argv[3]); 74 | 75 | if(argc>=5) codePage = GetCodePage(argv[4]); 76 | 77 | ExtractPackage(pacPath, dirPath,codePage); 78 | } 79 | else 80 | { 81 | printf("ERROR: Unknown command."); 82 | return 1; 83 | } 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /packFunc/enc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #undef min 7 | #undef max 8 | 9 | static std::wstring AnsiToUnicode(const std::string& source, int codePage) 10 | { 11 | if (source.length() == 0) { 12 | return std::wstring(); 13 | } 14 | if (source.length() > (size_t)std::numeric_limits::max()) { 15 | return std::wstring(); 16 | } 17 | int length = MultiByteToWideChar(codePage, 0, source.c_str(), (int)source.length(), NULL, 0); 18 | if (length <= 0) { 19 | return std::wstring(); 20 | } 21 | std::wstring output(length, L'\0'); 22 | if (MultiByteToWideChar(codePage, 0, source.c_str(), (int)source.length(), (LPWSTR)output.data(), (int)output.length() + 1) == 0) { 23 | return std::wstring(); 24 | } 25 | return output; 26 | } 27 | 28 | static std::string UnicodeToAnsi(const std::wstring& source, int codePage) 29 | { 30 | if (source.length() == 0) { 31 | return std::string(); 32 | } 33 | if (source.length() > (size_t)std::numeric_limits::max()) { 34 | return std::string(); 35 | } 36 | int length = WideCharToMultiByte(codePage, 0, source.c_str(), (int)source.length(), NULL, 0, NULL, NULL); 37 | if (length <= 0) { 38 | return std::string(); 39 | } 40 | std::string output(length, '\0'); 41 | if (WideCharToMultiByte(codePage, 0, source.c_str(), (int)source.length(), (LPSTR)output.data(), (int)output.length() + 1, NULL, NULL) == 0) { 42 | return std::string(); 43 | } 44 | return output; 45 | } 46 | 47 | static std::string AnsiToUTF8(const std::string& source) 48 | { 49 | if(source.length()==0) return std::string(); 50 | 51 | if(source.length()>(size_t)std::numeric_limits::max()) return std::string(); 52 | 53 | int utf16Length = MultiByteToWideChar(CP_ACP,0,source.c_str(),(int)source.length(),NULL,0); 54 | 55 | if(utf16Length<=0) return std::string(); 56 | 57 | std::wstring utf16String(utf16Length,'\0'); 58 | 59 | if(MultiByteToWideChar(CP_ACP,0,source.c_str(),(int)source.length(),(LPWSTR)utf16String.data(),utf16Length+1)==0) return std::string(); 60 | 61 | int utf8Length = WideCharToMultiByte(CP_UTF8,0,utf16String.data(),utf16Length,NULL,0,NULL,NULL); 62 | 63 | std::string output(utf8Length,'\0'); 64 | 65 | if(WideCharToMultiByte(CP_UTF8,0,utf16String.data(),utf16Length,(LPSTR)output.data(),utf8Length+1,NULL,NULL)==0) return std::string(); 66 | 67 | return output; 68 | 69 | } -------------------------------------------------------------------------------- /huffman/huffmanDecoder.cpp: -------------------------------------------------------------------------------- 1 | #include "huffmanDecoder.h" 2 | 3 | #include 4 | 5 | uint32_t HuffmanDecoder::GetBits(uint32_t needBit) 6 | { 7 | uint32_t result = 0; 8 | 9 | uint32_t readBit = 0; 10 | 11 | while (needBit > 0) 12 | { 13 | if (!this->_bitCount) 14 | { 15 | if (this->_byteCount >= this->_bufferSize) 16 | { 17 | return 0; 18 | } 19 | this->_curValue = this->_buffer[this->_byteCount++]; 20 | this->_bitCount = 8; 21 | } 22 | 23 | readBit = needBit > this->_bitCount ? this->_bitCount : needBit; 24 | 25 | result <<= readBit; 26 | result |= this->_curValue >> (this->_bitCount - readBit); 27 | this->_curValue &= (1 << (this->_bitCount - readBit)) - 1; 28 | 29 | this->_bitCount -= readBit; 30 | needBit -= readBit; 31 | } 32 | 33 | return result; 34 | } 35 | 36 | void HuffmanDecoder::ParseBitStreamToHuffmanTree() 37 | { 38 | std::stack> nodeStack; 39 | 40 | this->_root = std::make_shared(); 41 | 42 | nodeStack.push(this->_root); 43 | 44 | auto curNode = nodeStack.top(); 45 | 46 | while (!nodeStack.empty()) 47 | { 48 | 49 | if (this->GetBits(1)) 50 | { 51 | 52 | if (curNode->leftNode == nullptr) 53 | { 54 | nodeStack.push(curNode); 55 | curNode->leftNode = std::make_shared(); 56 | curNode = curNode->leftNode; 57 | } 58 | else 59 | { 60 | nodeStack.push(curNode); 61 | curNode->rightNode = std::make_shared(); 62 | curNode = curNode->rightNode; 63 | } 64 | } 65 | else 66 | { 67 | curNode->symbol = static_cast(this->GetBits(8)); 68 | 69 | curNode = nodeStack.top(); 70 | nodeStack.pop(); 71 | 72 | if (curNode->rightNode == nullptr) 73 | { 74 | curNode->rightNode = std::make_shared(); 75 | curNode = curNode->rightNode; 76 | } 77 | } 78 | } 79 | } 80 | 81 | uint32_t HuffmanDecoder::Decode(uint8_t* const decodeBuffer, uint32_t decodeBufferSize) 82 | { 83 | if (!decodeBuffer) 84 | return 0; 85 | 86 | this->ParseBitStreamToHuffmanTree(); 87 | 88 | uint32_t decodeCount = 0; 89 | 90 | auto curNode = this->_root; 91 | 92 | while (true) 93 | { 94 | 95 | if (decodeCount >= decodeBufferSize) 96 | break; 97 | 98 | if (!this->GetBits(1)) 99 | { 100 | curNode = curNode->leftNode; 101 | } 102 | else 103 | { 104 | curNode = curNode->rightNode; 105 | } 106 | 107 | if (curNode->leftNode == nullptr && curNode->rightNode == nullptr) 108 | { 109 | decodeBuffer[decodeCount++] = curNode->symbol; 110 | curNode = this->_root; 111 | } 112 | } 113 | 114 | return decodeCount; 115 | } -------------------------------------------------------------------------------- /huffman/huffmanEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include "huffmanEncoder.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void HuffmanEncoder::SetBits(std::vector &buffer, uint32_t setBit, uint32_t setValue) 9 | { 10 | uint32_t writeBit = 0; 11 | 12 | while (setBit > 0) 13 | { 14 | if (!this->_bitCount) 15 | { 16 | buffer.emplace_back(this->_curValue); 17 | this->_bitCount = 8; 18 | this->_curValue = 0; 19 | } 20 | 21 | writeBit = this->_bitCount < setBit ? this->_bitCount : setBit; 22 | 23 | uint32_t mask = (1 << writeBit) - 1; 24 | 25 | uint32_t writeBitValue = (setValue >> (setBit - writeBit)) & mask; 26 | 27 | this->_curValue |= writeBitValue << (this->_bitCount - writeBit); 28 | 29 | setBit -= writeBit; 30 | this->_bitCount -= writeBit; 31 | } 32 | } 33 | 34 | void HuffmanEncoder::ParseHuffmanTreeToBitStream(std::vector &streamBuffer, const std::shared_ptr &node) 35 | { 36 | if (node->leftNode == nullptr && node->rightNode == nullptr) 37 | { 38 | this->SetBits(streamBuffer, 1, 0); 39 | this->SetBits(streamBuffer, 8, node->symbol); 40 | } 41 | else 42 | { 43 | this->SetBits(streamBuffer, 1, 1); 44 | if (node->leftNode != nullptr) 45 | this->ParseHuffmanTreeToBitStream(streamBuffer, node->leftNode); 46 | if (node->rightNode != nullptr) 47 | this->ParseHuffmanTreeToBitStream(streamBuffer, node->rightNode); 48 | } 49 | } 50 | 51 | void HuffmanEncoder::ConstructionPath(std::shared_ptr &node, const std::vector &parentPath, const uint32_t branch) 52 | { 53 | 54 | node->path.assign(parentPath.begin(), parentPath.end()); 55 | node->path.emplace_back(branch); 56 | 57 | if (node->leftNode != nullptr) 58 | { 59 | this->ConstructionPath(node->leftNode, node->path, 0); 60 | } 61 | if (node->rightNode != nullptr) 62 | { 63 | this->ConstructionPath(node->rightNode, node->path, 1); 64 | } 65 | } 66 | 67 | std::vector HuffmanEncoder::Encode(uint8_t *const buffer, uint32_t bufferSize) 68 | { 69 | 70 | std::deque> nodeDueqe; 71 | std::map> nodeMap; 72 | 73 | for (uint32_t i = 0; i < bufferSize; ++i) 74 | { 75 | uint8_t c = buffer[i]; 76 | 77 | if (nodeMap[c] == nullptr) 78 | { 79 | nodeDueqe.emplace_back(std::make_shared()); 80 | 81 | nodeMap[c] = nodeDueqe.back(); 82 | 83 | nodeMap[c]->symbol = c; 84 | 85 | nodeMap[c]->weight = 1; 86 | } 87 | else 88 | { 89 | nodeMap[c]->weight++; 90 | } 91 | } 92 | 93 | if (nodeDueqe.size() > 2) 94 | std::sort(nodeDueqe.begin(), nodeDueqe.end(), 95 | [](std::shared_ptr sun, std::shared_ptr moon) 96 | { 97 | return sun->weight > moon->weight; 98 | }); 99 | 100 | std::shared_ptr curNode = nullptr; 101 | 102 | while (nodeDueqe.size() > 1) 103 | { 104 | curNode = std::make_shared(); 105 | 106 | auto leftNode = nodeDueqe.back(); 107 | nodeDueqe.pop_back(); 108 | auto rightNode = nodeDueqe.back(); 109 | nodeDueqe.pop_back(); 110 | 111 | curNode->leftNode = leftNode; 112 | curNode->rightNode = rightNode; 113 | 114 | curNode->weight = leftNode->weight + rightNode->weight; 115 | 116 | if (nodeDueqe.size() > 1) 117 | { 118 | if (curNode->weight > (nodeDueqe[nodeDueqe.size() - 1]->weight + nodeDueqe[nodeDueqe.size() - 2]->weight)) 119 | { 120 | nodeDueqe.insert(nodeDueqe.end() - 3, curNode); 121 | continue; 122 | } 123 | } 124 | 125 | nodeDueqe.emplace_back(curNode); 126 | } 127 | 128 | this->_root = curNode; 129 | 130 | if (this->_root->leftNode != nullptr) 131 | this->ConstructionPath(this->_root->leftNode, this->_root->path, 0); 132 | if (this->_root->rightNode != nullptr) 133 | this->ConstructionPath(this->_root->rightNode, this->_root->path, 1); 134 | 135 | std::vector encodedBuffer; 136 | 137 | encodedBuffer.reserve(bufferSize); 138 | 139 | this->ParseHuffmanTreeToBitStream(encodedBuffer, this->_root); 140 | 141 | for (uint32_t i = 0; i < bufferSize; i++) 142 | { 143 | uint8_t c = buffer[i]; 144 | 145 | auto node = nodeMap[c]; 146 | 147 | for (const auto &val : node->path) 148 | this->SetBits(encodedBuffer, 1, val); 149 | } 150 | 151 | if (this->_bitCount != 8) 152 | encodedBuffer.emplace_back(this->_curValue); 153 | 154 | return encodedBuffer; 155 | } -------------------------------------------------------------------------------- /packFunc/unpack.cpp: -------------------------------------------------------------------------------- 1 | #include "packFunc.h" 2 | 3 | #include "enc.hpp" 4 | #include "huffman/huffmanDecoder.h" 5 | #include "quote/header/zlib.h" 6 | #include "quote/header/zstd.h" 7 | 8 | // For SHCreateDirectoryExA 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #undef min 18 | #undef max 19 | 20 | using std::chrono::duration_cast; 21 | using std::chrono::milliseconds; 22 | using std::chrono::steady_clock; 23 | 24 | /** 25 | * @brief 写入文件 26 | * 27 | * @param path 要写入的目标文件 28 | * @param data 源缓冲区 29 | * @param size 缓冲区size 30 | * @return 函数执行结果 31 | */ 32 | bool WriteToFile(const std::wstring &path, const void *data, size_t size) 33 | { 34 | FILE *fp = _wfopen(path.c_str(), L"wb"); 35 | 36 | if (fp) 37 | { 38 | if (fwrite(data, size, 1, fp) == 1) 39 | { 40 | fclose(fp); 41 | return true; 42 | } 43 | 44 | fclose(fp); 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * @brief 解压文件到指定目录 52 | * 53 | * @param fp 封包文件 54 | * @param entries 文件索引 55 | * @param count 文件数量 56 | * @param compressionMethod 压缩方式 57 | * @param dirPath 输出目录路径 58 | * @param closeFile 是否关闭文件stream 59 | * @return 成功导出的文件数 60 | */ 61 | size_t ExtractEntry(FILE *fp, PackageEntry *entries, uint32_t count, uint32_t compressionMethod, const std::string &dirPath,int codePage ,bool closeFile) 62 | { 63 | std::vector uncompressedData; 64 | std::vector compressedData; 65 | size_t extractCount = 0; 66 | 67 | for (uint32_t i = 0; i < count; i++) 68 | { 69 | // printf("Extract %s\n", entries[i].Name); 70 | 71 | fseek(fp, entries[i].Position, SEEK_SET); 72 | 73 | if (compressionMethod != 0) // 判断文件是否被压缩 74 | { 75 | uncompressedData.resize(entries[i].OriginalSize); 76 | compressedData.resize(entries[i].CompressedSize); 77 | 78 | if (entries[i].OriginalSize != entries[i].CompressedSize) 79 | { 80 | fread(compressedData.data(), entries[i].CompressedSize, 1, fp); 81 | 82 | if (compressionMethod == 4) 83 | { 84 | uLong sourceLen = entries[i].CompressedSize; 85 | uLongf destLen = entries[i].OriginalSize; 86 | 87 | int result = uncompress((Bytef *)uncompressedData.data(), &destLen, (Bytef *)compressedData.data(), sourceLen); 88 | 89 | if (result != Z_OK) 90 | { 91 | printf("ERROR: Failed to uncompress %s with zlib.\n", entries[i].Name); 92 | continue; 93 | } 94 | } 95 | else if (compressionMethod == 7) 96 | { 97 | size_t result = ZSTD_decompress(uncompressedData.data(), entries[i].OriginalSize, compressedData.data(), entries[i].CompressedSize); 98 | 99 | if (ZSTD_isError(result)) 100 | { 101 | printf("ERROR: Failed to uncompress %s with zstd(%s).\n", entries[i].Name, ZSTD_getErrorName(result)); 102 | continue; 103 | } 104 | } 105 | } 106 | else 107 | { 108 | fread(uncompressedData.data(), entries[i].CompressedSize, 1, fp); 109 | } 110 | } 111 | else 112 | { 113 | uncompressedData.resize(entries[i].CompressedSize); 114 | fread(uncompressedData.data(), entries[i].CompressedSize, 1, fp); 115 | } 116 | 117 | std::wstring path = AnsiToUnicode(dirPath, CP_ACP) + L"\\" + AnsiToUnicode(entries[i].Name, codePage); 118 | 119 | if (WriteToFile(path, uncompressedData.data(), uncompressedData.size())) extractCount++; 120 | } 121 | 122 | if (closeFile) 123 | { 124 | fclose(fp); 125 | } 126 | 127 | return extractCount; 128 | } 129 | 130 | /** 131 | * @brief 多线程导出文件 132 | * 133 | * @param pacPath 目标封包 134 | * @param entries 封包内文件信息list 135 | * @param count 封包文件计数 136 | * @param compressionMethod 封包压缩方式 137 | * @param dirPath 导出的目标文件夹 138 | * @return 成功导出的文件数 139 | */ 140 | size_t ExtractEntryMT(const std::string &pacPath, PackageEntry *entries, uint32_t count, uint32_t compressionMethod, const std::string &dirPath,int codePage) 141 | { 142 | auto maxThreads = std::thread::hardware_concurrency(); 143 | auto filesPerThread = (uint32_t)ceilf((float)count / (float)maxThreads); // 单线程处理的文件数,向上取整 144 | 145 | size_t extractCount = 0; // 导出的文件数 146 | uint32_t remaining = count; // 未处理的文件数 147 | uint32_t j = 0; 148 | 149 | std::list> tasks; 150 | 151 | for (uint32_t i = 0; i < maxThreads; i++) 152 | { 153 | uint32_t processCount = std::min(remaining, filesPerThread); // 当前线程要处理的文件数 154 | remaining -= processCount; 155 | 156 | // printf("Start worker thread to processing [%d,%d]\n", j, j + processCount - 1); // 打印当前线程处理的文件的index 157 | 158 | auto fp = fopen(pacPath.c_str(), "rb"); 159 | 160 | if (fp) 161 | { 162 | auto startEntry = entries + j; 163 | auto task = std::async(std::launch::async, ExtractEntry, fp, startEntry, processCount, compressionMethod, dirPath, codePage ,true); 164 | tasks.emplace_back(std::move(task)); 165 | } 166 | 167 | j += processCount; 168 | } 169 | 170 | for (auto &t : tasks) extractCount += t.get(); 171 | 172 | return extractCount; 173 | } 174 | 175 | /** 176 | * @brief 解包 177 | * 178 | * @param pacPath 封包文件路径 179 | * @param dirPath 输出目录路径 180 | * @return 函数执行结果 181 | */ 182 | bool ExtractPackage(const std::string &pacPath, const std::string &dirPath,int codePage) 183 | { 184 | FILE *fp = fopen(pacPath.c_str(), "rb"); 185 | 186 | if (!fp) 187 | { 188 | printf("ERROR: Failed to open package file."); 189 | return false; 190 | } 191 | 192 | uint8_t magic[4]; 193 | fread(magic, 4, 1, fp); 194 | 195 | if (magic[0] != 0x50 || magic[1] != 0x41 || magic[2] != 0x43) 196 | { 197 | fclose(fp); 198 | printf("ERROR: Invalid package file."); 199 | return false; 200 | } 201 | 202 | uint32_t entryCount; 203 | uint32_t compressionMethod; 204 | uint32_t compressedIndexSize; 205 | 206 | fread(&entryCount, 4, 1, fp); 207 | fread(&compressionMethod, 4, 1, fp); 208 | 209 | printf("Total %d files in the package.\n", entryCount); 210 | 211 | fseek(fp, -4, SEEK_END); 212 | fread(&compressedIndexSize, 4, 1, fp); 213 | 214 | std::vector compressedIndex; 215 | compressedIndex.resize(compressedIndexSize); 216 | 217 | fseek(fp, -(compressedIndexSize + 4), SEEK_END); 218 | fread(compressedIndex.data(), compressedIndexSize, 1, fp); 219 | 220 | // Decrypt index 221 | for (uint32_t i = 0; i < compressedIndexSize; i++) 222 | { 223 | compressedIndex[i] = ~compressedIndex[i]; 224 | } 225 | 226 | uint32_t indexSize = sizeof(PackageEntry) * entryCount; 227 | 228 | std::vector index; 229 | index.resize(indexSize); 230 | 231 | // Decompress index 232 | HuffmanDecoder huffmanDecoder(compressedIndex.data(),compressedIndexSize); 233 | huffmanDecoder.Decode(index.data(),indexSize); 234 | 235 | // 偷懒方式创建文件夹 236 | SHCreateDirectoryExA(NULL, dirPath.c_str(), NULL); 237 | 238 | auto tp1 = steady_clock::now(); 239 | 240 | // ExtractEntry(fp, (PackageEntry*)index.data(), entryCount, compressionMethod, dirPath, false); 241 | size_t extractCount = ExtractEntryMT(pacPath, (PackageEntry *)index.data(), entryCount, compressionMethod, dirPath,codePage); 242 | 243 | auto tp2 = steady_clock::now(); 244 | 245 | auto ms = duration_cast(tp2 - tp1).count(); 246 | 247 | printf("Extracted %d files in %llu ms.\n", extractCount, ms); 248 | 249 | fclose(fp); 250 | 251 | return true; 252 | } -------------------------------------------------------------------------------- /packFunc/topack.cpp: -------------------------------------------------------------------------------- 1 | #include "packFunc.h" 2 | 3 | #include "enc.hpp" 4 | #include "huffman/huffmanEncoder.h" 5 | #include "quote/header/zlib.h" 6 | #include "quote/header/zstd.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using std::chrono::duration_cast; 15 | using std::chrono::milliseconds; 16 | using std::chrono::steady_clock; 17 | 18 | /** 19 | * @brief 遍历目标文件夹以及子文件夹,获取文件目录 20 | * 21 | * @param[in] path 目标文件夹路径 22 | * @return std::list 文件名list 23 | */ 24 | std::list GetFilesInDirectory(const std::string &path) 25 | { 26 | // Result file paths 27 | std::list filePaths; 28 | // Directory paths to search 29 | std::list directoryPaths; //需要遍历的文件夹list,用list的方式来避免递归 30 | 31 | // Add root path to start 32 | directoryPaths.emplace_back(path); //添加目标文件夹 33 | 34 | while (!directoryPaths.empty()) 35 | { 36 | // Get path 37 | std::string currentPath = std::move(directoryPaths.front()); 38 | directoryPaths.pop_front(); 39 | 40 | // Make full path search pattern 41 | std::string pattern; 42 | pattern.reserve(currentPath.size() + 4); //预先设置容量,防止再分配 43 | pattern.append(currentPath); 44 | pattern.append("\\*.*"); 45 | 46 | // Start search 47 | _finddata_t data; 48 | intptr_t handle = _findfirst(pattern.c_str(), &data); 49 | 50 | if (handle == -1) 51 | { 52 | // Ignore invalid path 53 | continue; 54 | } 55 | 56 | do 57 | { 58 | size_t nameLength = strlen(data.name); 59 | 60 | if (data.attrib & _A_SUBDIR) 61 | { 62 | // We need ignore special directory e.g "." and ".." 63 | int j = 0; 64 | 65 | while (j < nameLength) 66 | { 67 | if (data.name[j] != '.') //文件夹名不能带. 68 | { 69 | // Should be a valid subdirectory name 70 | break; 71 | } 72 | j++; 73 | } 74 | 75 | if (j < nameLength) 76 | { 77 | // Append subdirectory path to the list and search later 78 | std::string newPath; 79 | newPath.reserve(currentPath.size() + 1 + nameLength); 80 | newPath.append(currentPath); 81 | newPath.push_back('\\'); 82 | newPath.append(data.name); 83 | directoryPaths.emplace_back(std::move(newPath)); 84 | } 85 | } 86 | else 87 | { 88 | // Append file path to the result 89 | std::string newPath; 90 | newPath.reserve(currentPath.size() + 1 + nameLength); 91 | newPath.append(currentPath); 92 | newPath.push_back('\\'); 93 | newPath.append(data.name); 94 | filePaths.emplace_back(std::move(newPath)); 95 | } 96 | 97 | } while (_findnext(handle, &data) == 0); 98 | 99 | _findclose(handle); 100 | } 101 | 102 | return filePaths; 103 | } 104 | 105 | /** 106 | * @brief 根据文件路径获取文件名 107 | * 108 | * @param path 文件路径 109 | * @return std::string 文件名 110 | */ 111 | std::string GetFileName(const std::string &path) 112 | { 113 | size_t pos; 114 | 115 | //判断分隔符为\或/的两种情况 116 | pos = path.find_last_of('\\'); 117 | 118 | if (pos != std::string::npos) 119 | { 120 | return path.substr(pos + 1); 121 | } 122 | 123 | pos = path.find_last_of('/'); 124 | 125 | if (pos != std::string::npos) 126 | { 127 | return path.substr(pos + 1); 128 | } 129 | 130 | return path; 131 | } 132 | 133 | /** 134 | * @brief 读取文件 135 | * @param[in] 目标文件 136 | * 137 | * @return std::vector 读取后的缓冲区 138 | */ 139 | std::vector ReadFileData(const std::string &path) 140 | { 141 | FILE *fp = fopen(path.c_str(), "rb"); 142 | 143 | if (fp) //判断io 144 | { 145 | fseek(fp, 0, SEEK_END); 146 | long size = ftell(fp); 147 | fseek(fp, 0, SEEK_SET); 148 | 149 | if (size > 0) 150 | { 151 | std::vector buffer; 152 | buffer.resize(size); 153 | 154 | if (fread(buffer.data(), size, 1, fp) == 1) 155 | { 156 | fclose(fp); 157 | return buffer; 158 | } 159 | } 160 | 161 | fclose(fp); 162 | } 163 | 164 | return {}; 165 | } 166 | 167 | ////////////////////////////////////////////////////////////////// 168 | // 单线程处理 169 | ////////////////////////////////////////////////////////////////// 170 | 171 | bool CreatePackage(const std::string &pacPath, const std::string &dirPath, int compressionMethod,int codePage) 172 | { 173 | auto tp1 = steady_clock::now(); 174 | 175 | auto files = GetFilesInDirectory(dirPath); //声明时调用函数初始化,编译器自动传入目标变量指针来优化,防止拷贝 176 | 177 | if (files.empty()) 178 | { 179 | printf("ERROR: No any files to pack."); 180 | return false; 181 | } 182 | 183 | std::vector entries; 184 | entries.resize(files.size()); 185 | 186 | auto fp = fopen(pacPath.c_str(), "wb"); 187 | 188 | if (!fp) 189 | { 190 | printf("ERROR: Failed to create package file."); 191 | return false; 192 | } 193 | 194 | uint8_t magic[] = {0x50, 0x41, 0x43, 0x75}; 195 | fwrite(magic, 4, 1, fp); 196 | 197 | // 这里先不写数量,因为读取或压缩的时候可能会发生错误,跳过一些文件,所以后面再更新数量 198 | uint32_t entryCount = 0; 199 | 200 | fwrite(&entryCount, 4, 1, fp); 201 | fwrite(&compressionMethod, 4, 1, fp); 202 | 203 | // 实际处理了的文件数量 204 | int i = 0; 205 | 206 | std::vector compressedData; 207 | 208 | for (auto &path : files) //遍历所有文件 209 | { 210 | auto name = GetFileName(path); 211 | 212 | if(codePage==CP_UTF8) AnsiToUTF8(name); 213 | 214 | if (name.size() >= sizeof(PackageEntry::Name)) //防止文件名过长 215 | { 216 | printf("WARNING: File name '%s' too long.", name.c_str()); 217 | continue; 218 | } 219 | 220 | auto data = ReadFileData(path); 221 | 222 | if (data.empty()) //防止读取失败 223 | { 224 | printf("ERROR: Failed to read file '%s'.", path.c_str()); 225 | continue; 226 | } 227 | 228 | uint32_t position = ftell(fp); 229 | uint32_t originalSize = data.size(); 230 | uint32_t compressedSize = originalSize; 231 | 232 | //根据文件扩展名,排除掉一些文件,不进行压缩 233 | auto extension = name.substr(name.find_last_of('.')); 234 | if (extension == ".ogg" || extension == ".png" || extension == ".wav" || extension == ".fnt") 235 | { 236 | fwrite(data.data(), originalSize, 1, fp); 237 | } 238 | else 239 | { 240 | 241 | if (compressionMethod == 4) 242 | { 243 | uLong sourceLen = data.size(); 244 | uLong destLen = compressBound(sourceLen); 245 | 246 | compressedData.resize(destLen); 247 | 248 | // int result = compress2(compressedData.data(), &destLen, data.data(), sourceLen, Z_BEST_COMPRESSION); 249 | int result = compress(compressedData.data(), &destLen, data.data(), sourceLen); 250 | 251 | if (result != Z_OK) 252 | { 253 | printf("ERROR: Failed to compress file '%s' with zlib.", path.c_str()); 254 | continue; 255 | } 256 | 257 | compressedData.resize(destLen); 258 | compressedSize = destLen; 259 | 260 | fwrite(compressedData.data(), compressedSize, 1, fp); 261 | } 262 | else if (compressionMethod == 7) 263 | { 264 | size_t srcSize = data.size(); 265 | size_t dstSize = ZSTD_compressBound(srcSize); 266 | 267 | compressedData.resize(dstSize); 268 | 269 | size_t result = ZSTD_compress(compressedData.data(), dstSize, data.data(), srcSize, ZSTD_maxCLevel()); 270 | 271 | if (ZSTD_isError(result)) 272 | { 273 | printf("ERROR: Failed to compress file '%s' with zstd.", path.c_str()); 274 | continue; 275 | } 276 | 277 | compressedData.resize(result); 278 | compressedSize = result; 279 | 280 | fwrite(compressedData.data(), compressedSize, 1, fp); 281 | } 282 | else 283 | { 284 | fwrite(data.data(), originalSize, 1, fp); 285 | } 286 | } 287 | auto &entry = entries[i]; 288 | 289 | strcpy_s(entry.Name, name.c_str()); 290 | entry.Position = position; 291 | entry.OriginalSize = originalSize; 292 | entry.CompressedSize = compressedSize; 293 | 294 | i++; 295 | } 296 | 297 | entryCount = i; 298 | 299 | uint8_t* const index = reinterpret_cast(entries.data()); 300 | auto indexSize = sizeof(PackageEntry) * entryCount; 301 | 302 | HuffmanEncoder huffmanEncoder; 303 | std::vector compressedIndex = huffmanEncoder.Encode(index,indexSize); 304 | 305 | uint32_t compressedIndexSize = compressedIndex.size(); 306 | 307 | // Encrypt index 308 | for (uint32_t i = 0; i < compressedIndexSize; i++) 309 | { 310 | compressedIndex[i] = ~compressedIndex[i]; 311 | } 312 | 313 | fwrite(compressedIndex.data(), compressedIndexSize, 1, fp); 314 | fwrite(&compressedIndexSize, 4, 1, fp); 315 | 316 | // 回去更新文件数量 317 | fseek(fp, 4, SEEK_SET); 318 | fwrite(&entryCount, 4, 1, fp); 319 | 320 | fflush(fp); //刷新缓冲区 321 | fclose(fp); 322 | 323 | auto tp2 = steady_clock::now(); 324 | 325 | auto ms = duration_cast(tp2 - tp1).count(); 326 | 327 | printf("[ST] Packed %d files in %llu ms.\n", entryCount, ms); 328 | 329 | return true; 330 | } 331 | 332 | ////////////////////////////////////////////////////////////////// 333 | // 多线程处理 334 | ////////////////////////////////////////////////////////////////// 335 | 336 | struct FileData 337 | { 338 | std::string Name; //文件名 339 | std::vector Data; //压缩后数据的缓冲区 340 | uint32_t OriginalSize = 0; //原始size 341 | uint32_t CompressedSize = 0; //压缩后size 342 | }; 343 | 344 | /** 345 | * @brief 346 | * @param[in] path 目标文件 347 | * @param[in] compressionMethod 压缩方式 348 | * 349 | * @return FileDate 压缩后写入封包需要的信息 350 | */ 351 | FileData ReadAndCompressFile(const std::string &path, int compressionMethod,int codePage) 352 | { 353 | auto name = GetFileName(path); 354 | 355 | if(codePage==CP_UTF8) name = AnsiToUTF8(name); 356 | 357 | if (name.size() >= sizeof(PackageEntry::Name)) 358 | { 359 | printf("WARNING: File name '%s' too long.", name.c_str()); 360 | return {}; 361 | } 362 | 363 | auto data = ReadFileData(path); 364 | 365 | if (data.empty()) 366 | { 367 | printf("ERROR: Failed to read file '%s'.", path.c_str()); 368 | return {}; 369 | } 370 | 371 | std::vector compressedData; 372 | 373 | //根据文件扩展名,排除掉一些文件,不进行压缩 374 | auto extension = name.substr(name.find_last_of('.')); 375 | 376 | if (extension == ".ogg" || extension == ".png" || extension == ".wav" || extension == ".fnt") 377 | { 378 | size_t srcSize = data.size(); 379 | 380 | return {std::move(name), std::move(data), (uint32_t)srcSize, (uint32_t)srcSize}; 381 | } 382 | else 383 | { 384 | if (compressionMethod == 4) 385 | { 386 | uLong sourceLen = data.size(); 387 | uLong destLen = compressBound(sourceLen); 388 | 389 | compressedData.resize(destLen); 390 | 391 | int result = compress2(compressedData.data(), &destLen, data.data(), sourceLen, Z_BEST_COMPRESSION); 392 | 393 | if (result != Z_OK) 394 | { 395 | printf("ERROR: Failed to compress file '%s' with zlib.", path.c_str()); 396 | } 397 | 398 | compressedData.resize(destLen); 399 | 400 | return {std::move(name), std::move(compressedData), (uint32_t)data.size(), destLen}; 401 | } 402 | else if (compressionMethod == 7) 403 | { 404 | size_t srcSize = data.size(); 405 | size_t dstSize = ZSTD_compressBound(srcSize); 406 | 407 | compressedData.resize(dstSize); 408 | 409 | size_t result = ZSTD_compress(compressedData.data(), dstSize, data.data(), srcSize, ZSTD_maxCLevel()); 410 | 411 | if (ZSTD_isError(result)) 412 | { 413 | printf("ERROR: Failed to compress file '%s' with zstd.", path.c_str()); 414 | } 415 | 416 | compressedData.resize(result); 417 | 418 | return {std::move(name), std::move(compressedData), (uint32_t)data.size(), (uint32_t)result}; 419 | } 420 | else 421 | { 422 | size_t srcSize = data.size(); 423 | 424 | return {std::move(name), std::move(data), (uint32_t)srcSize,(uint32_t)srcSize}; 425 | } 426 | } 427 | } 428 | 429 | /** 430 | * @brief 多线程压缩 431 | * 432 | * @param pacPath 要写入的目标封包 433 | * @param dirPath 源文件夹 434 | * @param compressionMethod 压缩方式 435 | * @return 函数执行结果 436 | */ 437 | bool CreatePackageMT(const std::string &pacPath, const std::string &dirPath, int compressionMethod,int codePage) 438 | { 439 | auto tp1 = steady_clock::now(); 440 | 441 | auto files = GetFilesInDirectory(dirPath); 442 | 443 | if (files.empty()) 444 | { 445 | printf("ERROR: No any files to pack."); 446 | return false; 447 | } 448 | 449 | // 创建索引数据块,内存连续 450 | std::vector entries; 451 | entries.resize(files.size()); 452 | 453 | FILE *fp = fopen(pacPath.c_str(), "wb"); 454 | 455 | if (!fp) 456 | { 457 | printf("ERROR: Failed to create package file."); 458 | return false; 459 | } 460 | 461 | printf("Total %d files to pack.\n", files.size()); 462 | 463 | // 写文件头 464 | uint8_t magic[] = {0x50, 0x41, 0x43, 0x75}; 465 | fwrite(magic, 4, 1, fp); 466 | 467 | // 这里先不写数量,因为读取或压缩的时候可能会发生错误,跳过一些文件,所以后面再更新数量 468 | uint32_t entryCount = 0; 469 | 470 | fwrite(&entryCount, 4, 1, fp); 471 | fwrite(&compressionMethod, 4, 1, fp); 472 | 473 | // 实际处理了的文件数量 474 | uint32_t i = 0; 475 | 476 | // 取CPU线程数量 477 | uint32_t maxThreads = std::thread::hardware_concurrency(); 478 | 479 | std::vector> tasks; //任务池 480 | tasks.reserve(maxThreads); 481 | 482 | while (!files.empty()) 483 | { 484 | // 创建线程并行读取和压缩 485 | 486 | tasks.clear(); 487 | 488 | for (uint32_t n = 0; n < maxThreads; n++) 489 | { 490 | if (files.empty()) 491 | break; 492 | 493 | // 取出一个文件名然后创建线程来读取 494 | auto &path = files.front(); 495 | auto task = std::async(std::launch::async, ReadAndCompressFile, std::move(path), compressionMethod,codePage); //std::launch::async 强制创建新线程执行 496 | tasks.emplace_back(std::move(task)); 497 | files.pop_front(); 498 | } 499 | 500 | // 获取文件数据 501 | 502 | for (auto &task : tasks) 503 | { 504 | auto result = task.get(); 505 | 506 | if (result.Data.empty()) 507 | continue; // 读取或者压缩失败了 508 | 509 | auto &entry = entries[i]; 510 | 511 | strcpy_s(entry.Name, result.Name.c_str()); 512 | entry.Position = ftell(fp); 513 | entry.OriginalSize = result.OriginalSize; 514 | entry.CompressedSize = result.CompressedSize; 515 | 516 | fwrite(result.Data.data(), result.Data.size(), 1, fp); 517 | 518 | i++; 519 | } 520 | } 521 | 522 | entryCount = i; 523 | 524 | uint8_t* const index = reinterpret_cast(entries.data()); 525 | auto indexSize = sizeof(PackageEntry) * entryCount; 526 | 527 | // 压缩索引 528 | HuffmanEncoder huffmanEncoder; 529 | std::vector compressedIndex = huffmanEncoder.Encode(index,indexSize); 530 | 531 | uint32_t compressedIndexSize = compressedIndex.size(); 532 | 533 | // 加密 534 | for (uint32_t i = 0; i < compressedIndexSize; i++) 535 | { 536 | compressedIndex[i] = ~compressedIndex[i]; 537 | } 538 | 539 | fwrite(compressedIndex.data(), compressedIndexSize, 1, fp); 540 | fwrite(&compressedIndexSize, 4, 1, fp); 541 | 542 | // 回去更新文件数量 543 | fseek(fp, 4, SEEK_SET); 544 | fwrite(&entryCount, 4, 1, fp); 545 | 546 | fflush(fp); 547 | fclose(fp); 548 | 549 | auto tp2 = steady_clock::now(); 550 | 551 | auto ms = duration_cast(tp2 - tp1).count(); 552 | 553 | printf("[MT] Packed %d files in %llu ms.\n", entryCount, ms); 554 | 555 | return true; 556 | } 557 | --------------------------------------------------------------------------------