/* * This file is part of VoidArchiveTool. * * Copyright (C) 2025 Yanczi * * Void Archive Toolis free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "CreateCargo.h" CreateCargo::CreateCargo() : signature(SIGNATURE) , extension(EXTENSION) , version(VERSION) , methodFlags(0) , xxhState(XXH64_createState()) , offset(0) , hppKey(false) { // TODO Auto-generated constructor stub XXH64_reset(xxhState, 0); } CreateCargo::~CreateCargo() { // TODO Auto-generated destructor stub } //----------------------------------------------------------------------------- // Punk wejścia do tworzenia archivum //----------------------------------------------------------------------------- bool CreateCargo::Create(const std::string& path, const uint8_t& flag) { cargoFile = path + "." + extension; catalogPath = path; methodFlags = flag; //Sprawdzanie pakowanego kontentu if (!std::filesystem::is_directory(path)) { std::cerr << "Error: The specified directory is a file!" << std::endl; return false; } if (!std::filesystem::exists(path)) { std::cerr << "Error: The specified directory does not exist!" << std::endl; return false; } // Pobieranie listy plików wyjątków if (flag == -1) { std::string filterFile = path + ".json"; if (!std::filesystem::exists(filterFile)) { std::cerr << "Error: Missing " << filterFile << " file!" << std::endl; return false; } GetFilters(filterFile); } //Pobieranie listy plików do spakowania std::cout << "Creating a file list..." << std::endl; if (!GetFileList(path)) { std::cerr << "Error: The specified directory contains no files!" << std::endl; return false; } // Utworzenie kontenera cargo.open(cargoFile, std::ios::binary); //Rozpocznij process zapisywania danych do kontenera if (!WriteCargo()) { std::cerr << "Error: Failed to create container!" << std::endl; return false; } // Zapisywanie klucza szyfrującego if (flag == FILE_FLAG_ENCRYPT || flag == FILE_FLAG_ZIPENC || encList.size() > 0) { eman.saveKey(catalogPath, hppKey); } return true; } //----------------------------------------------------------------------------- // Tworzenie listy plików do spakowania //----------------------------------------------------------------------------- bool CreateCargo::GetFileList(const std::string& path) { for (const auto& entry : std::filesystem::directory_iterator(path)) { std::string tmpPath = entry.path().string(); if (std::filesystem::is_directory(tmpPath)) { GetFileList(tmpPath); } else { std::string fileRef = RemoveStartPath(PathToUnixLike(tmpPath)); if (fileRef.length() > 255) { std::cerr << "The file path is too long. It exceeds 255 characters." << std::endl; } else { PathConf pc; if (methodFlags != 0xAB) { pc.path = PathToUnixLike(tmpPath); pc.parameter = methodFlags; filesPaths.push_back(pc); } else { if (!FindOnTheList(ignoreList, fileRef) || !CheckFileExtension(fileRef, ignoreList)) { if (FindOnTheList(zipList, fileRef) || CheckFileExtension(fileRef, zipList)) { pc.parameter = FindOnTheList(encList, fileRef) || CheckFileExtension(fileRef, encList) ? FILE_FLAG_ZIPENC : FILE_FLAG_COMPRESS; } else { pc.parameter = FindOnTheList(encList, fileRef) || CheckFileExtension(fileRef, encList) ? FILE_FLAG_ENCRYPT : FILE_FLAG_RAW; } pc.path = PathToUnixLike(tmpPath); std::cout << pc.path << " - " << pc.parameter << std::endl; filesPaths.push_back(pc); } } } } } return filesPaths.size() > 0 ? true : false; } //----------------------------------------------------------------------------- // Konwersja ścieżki na unix like //----------------------------------------------------------------------------- std::string CreateCargo::PathToUnixLike(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); return path; } //----------------------------------------------------------------------------- // Kasowanie wskazanego katalogu ze ścieżki //----------------------------------------------------------------------------- std::string CreateCargo::RemoveStartPath(const std::filesystem::path& path) { std::filesystem::path cleanPath; auto it = path.begin(); if (it != path.end()) { ++it; } for (; it != path.end(); ++it) { cleanPath /= *it; } return cleanPath.string(); } //----------------------------------------------------------------------------- // Tworzenie nagłowka pliku //----------------------------------------------------------------------------- CargoHead CreateCargo::CreateCargoHead(const uint32_t& filesLen, const uint64_t& table) { CargoHead ch; ch.signature = signature; ch.table = table; ch.files = filesLen; return ch; } //----------------------------------------------------------------------------- // Sprawdza czy plik znajduje się na liście //----------------------------------------------------------------------------- void CreateCargo::computingBytes(const uint8_t& flag, std::vector& input, std::vector& output) { //Flaga aktywna sprawdza czy plik jest na liście. Jeśli jest to zwraca surowedane //Przeciwnie kompresuje dane ChunkManager cm(eman); switch (flag) { case FILE_FLAG_COMPRESS: output = cm.chunked(input, true, false); break; case FILE_FLAG_ENCRYPT: output = cm.chunked(input, false, true); break; case FILE_FLAG_ZIPENC: output = cm.chunked(input, true, true); break; default: output = std::move(input); break; } } //----------------------------------------------------------------------------- // Przygotowanie nagłówków i plików //----------------------------------------------------------------------------- std::vector CreateCargo::ComputingHeadFiles() { //Utwórz header TMP. Zabezpiecza Pierwsze bajty na właściwy nagłówek CargoHead cargoHead = CreateCargoHead(0, 0); offset += cargoHead.signature.length() + sizeof(cargoHead.files) + sizeof(cargoHead.table); //Zapisanie tymczasowego nagłowka jako rezerwacja miejsca cargo.write(cargoHead.signature.data(), cargoHead.signature.length()); cargo.write(reinterpret_cast(&cargoHead.table), sizeof(cargoHead.table)); cargo.write(reinterpret_cast(&cargoHead.files), sizeof(cargoHead.files)); std::vector filesTable; //Tworzenie nagłówków plików jednocześnie zapisywanie plików for (const auto& file : filesPaths) { std::string path = PathToUnixLike(RemoveStartPath(file.path)); std::ifstream f(file.path, std::ios::binary | std::ios::ate); std::cout << path << std::endl; //Obliczanie rozmiaru pliku size_t size = f.tellg(); f.seekg(0, std::ios::beg); if (size > MAX_FILE_SIZE) { std::cerr << path << " is too large. It exceeds " << MAX_FILE_SIZE / 1024 / 1024 / 1024 << "GB!" << std::endl; } else { XXH64_reset(xxhState, 0); //Wczytanie pliku do pamięci std::vector buffer(CHUNK_STREAM_256MB); uint64_t sizeFile = 0; const uint32_t chunkBlockSize = CHUNK_BLOCK_SIZE; const uint32_t quantity = (size + chunkBlockSize) / chunkBlockSize; const uint32_t lastChunkSize = size - (chunkBlockSize * (quantity - 1)); // Jeśli jest ustawiona flaga inna niż RAW // Dodaj do kontenera konfigurację chunków if (file.parameter != FILE_FLAG_RAW) { std::cout << "CHUNK PARAM" << std::endl; cargo.write(reinterpret_cast(&quantity), sizeof(quantity)); cargo.write(reinterpret_cast(&chunkBlockSize), sizeof(chunkBlockSize)); cargo.write(reinterpret_cast(&lastChunkSize), sizeof(lastChunkSize)); sizeFile = sizeof(quantity) + sizeof(chunkBlockSize) + sizeof(lastChunkSize); } // Strumieniowanie danych while (f.read(buffer.data(), CHUNK_STREAM_256MB) || f.gcount() > 0) { const int bufferSize = f.gcount(); buffer.resize(bufferSize); // Aktualizacja XXH64 XXH64_update(xxhState, buffer.data(), buffer.size()); if (file.parameter == FILE_FLAG_RAW) { // Zapisywanie strumienia do kontenera cargo.write(reinterpret_cast(buffer.data()), buffer.size()); sizeFile += bufferSize; } else { for (uint32_t ofs = 0; ofs < bufferSize; ofs += chunkBlockSize) { // Rozmiar chunka const uint32_t chunkSize = std::min(chunkBlockSize, bufferSize - ofs); auto begin = buffer.begin() + ofs; auto end = begin + chunkSize; // Skopiuj fragment danych do chunka std::vector chunk(begin, end); std::vector outChunk; // Przetwórz chunki i przetwórz if ((file.parameter & FILE_FLAG_COMPRESS) == FILE_FLAG_COMPRESS) { // Zaszyfruj i skompresuj lub tylko skompresuj outChunk = (file.parameter & FILE_FLAG_ENCRYPT) == FILE_FLAG_ENCRYPT ? eman.encrypt(cman.compress(chunk)) : cman.compress(chunk); } else { // Zaszyfruj lub skopiuj outChunk = (file.parameter & FILE_FLAG_ENCRYPT) == FILE_FLAG_ENCRYPT ? eman.encrypt(cman.compress(chunk)) : cman.compress(chunk); } const uint32_t outSize = outChunk.size(); cargo.write(reinterpret_cast(&outSize), sizeof(outSize)); sizeFile += sizeof(outSize); cargo.write(reinterpret_cast(outChunk.data()), outChunk.size()); sizeFile += outSize; } std::cout << "SIZE: " << sizeFile << std::endl; } } f.close(); //Tworzenie hashu CRC //const uint64_t crc = XXH64(buffer.data(), buffer.size(), 0); //Kompresjia //std::vector pakBuffer; //computingBytes(file.parameter, buffer, pakBuffer); FilesTable ft; ft.nameFile = path; ft.nameLen = path.length(); ft.offset = offset; ft.size = sizeFile; ft.flag = file.parameter; ft.crc = XXH64_digest(xxhState); //cargo.write(reinterpret_cast(pakBuffer.data()), pakBuffer.size()); filesTable.push_back(ft); offset += sizeFile; } } return filesTable; } //----------------------------------------------------------------------------- // Wczytanie filtrów wyjątków //----------------------------------------------------------------------------- void CreateCargo::GetFilters(const std::string& filterFile) { std::cout << "Downloading the exception list" << std::endl; std::ifstream file(filterFile); nlohmann::json jslist; file >> jslist; file.close(); // Lista plików do skompresowania if (jslist.contains(KEY_ZIP)) { zipList = jslist[KEY_ZIP].get>(); } // Lista plików do zaszyfrowania if (jslist.contains(KEY_ENCRYPT)) { encList = jslist[KEY_ENCRYPT].get>(); } // Lista plików do pominięcia if (jslist.contains(KEY_IGNORE)) { ignoreList = jslist[KEY_IGNORE].get>(); } hppKey = jslist.value("keyhpp", false); } //----------------------------------------------------------------------------- // Znajdź wskazany element na liście //----------------------------------------------------------------------------- bool CreateCargo::FindOnTheList(const std::vector& list, const std::string& element) { auto it = std::find(list.begin(), list.end(), element); return it == list.end() ? false : true; } //----------------------------------------------------------------------------- // Sprawdzanie rozszeżeń plików //----------------------------------------------------------------------------- bool CreateCargo::CheckFileExtension(const std::string& p, const std::vector& patterns) { std::filesystem::path _p = p; std::string ext = "*" + UpperString(_p.extension().string()); for (const auto& e : patterns) { std::string element = UpperString(e); if (element == ext) { return true; } } return false; } //----------------------------------------------------------------------------- // Zamień cały ciąg na duże litery //----------------------------------------------------------------------------- std::string CreateCargo::UpperString(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); return s; } //----------------------------------------------------------------------------- // Trworzenie archiwum //----------------------------------------------------------------------------- bool CreateCargo::WriteCargo() { std::cout << "Packing files..." << std::endl; uint32_t filesLen = filesPaths.size(); //Przygotowanie nagłówków plików i przetworzenie danych std::vector filesHead = ComputingHeadFiles(); //std::ofstream cargo(cargoFile, std::ios::binary); if (!cargo.is_open()) { std::cerr << "ERROR: Failed to create container " << cargoFile << std::endl; return false; } for (const auto& head : filesHead) { // Ścieżka do plików cargo.write(reinterpret_cast(&head.nameLen), sizeof(head.nameLen)); cargo.write(head.nameFile.data(), head.nameLen); cargo.write(reinterpret_cast(&head.offset), sizeof(head.offset)); cargo.write(reinterpret_cast(&head.size), sizeof(head.size)); cargo.write(reinterpret_cast(&head.crc), sizeof(head.crc)); cargo.write(reinterpret_cast(&head.flag), sizeof(head.flag)); } //Cofnij się na początek pliku cargo.seekp(std::ios::beg); //Utwórz header CargoHead cargoHead = CreateCargoHead(filesLen, offset); //Nadpisz tymczasowy nagłówek cargo.write(cargoHead.signature.data(), cargoHead.signature.length()); cargo.write(reinterpret_cast(&cargoHead.table), sizeof(cargoHead.table)); cargo.write(reinterpret_cast(&cargoHead.files), sizeof(cargoHead.files)); cargo.close(); std::cout << "The container was successfully created! " << cargoFile << std::endl; return true; }