025 Загрузка из архива
Пусть будет так, что, работа с данными разделяется на два типа.
Первая - сжатие и разжатие данных. Вторая - распаковка данных из архива.
Для первого будет использован fastlz. Для второго minizip.
zlib будет использован для PNG, ещё чего-то. Тратить время на его использование в сжатии данных не буду. Может быть кто-то реализует этот функционал за меня.
Добавлю - библиотека и алгоритм которым можно сжимать данные.
// Имя библиотеки_алгоритм, или просто имя если в ней один алгоритм
// megalib_deflate,
// megalib_lz77,
// megalib_lzma,
// Имеется:
// - fastlz (LZ77)
enum class bqCompressorType : uint32_t
{
// m_level:
// 1 скорость
// 2 сжатие
fastlz
};
Информация о сжатии
// Информация для сжатия и расжатия
struct bqCompressionInfo
{
bqCompressorType m_compressorType = bqCompressorType::fastlz;
// Когда надо сжать данные
// Установи размер данных и указатель на данные.
// после вызова Compress m_sizeCompressed и m_dataCompressed
// будут иметь значения
uint32_t m_sizeUncompressed = 0;
uint8_t* m_dataUncompressed = 0;
// Когда надо расжать
// Устанавливаем размер сжатых данных, указатель на сжатые данные.
// Так-же должен знать m_sizeUncompressed и надо выделить память для распакованных
// данных в m_dataUncompressed
// После вызова Decompress m_dataUncompressed будет иметь расжатые данные
uint32_t m_sizeCompressed = 0;
uint8_t* m_dataCompressed = 0;
// Смотри CompressorType. Если значение не верно
// будет использовано значение по умолчанию.
uint32_t m_level = 2;
};
Эта вещь для распаковки файла. Зависит от типа библиотеки которая используется. minizip использует свой тип для файла. оно находится в m_implementation Возможно использование других библиотек для работы с другими архивами. Наверно. Пока ничего другого не имею, и не нужно. Можно поразмышлять как сделать распаковку используя minizip и zlib (gz файл).
// Структура с описанием файла архива.
// Путь к файлу и специфичные данные
struct bqArchiveZipFile
{
bqStringA m_fileName;
void* m_implementation = 0;
};
Интерфейс для работы
class bqArchiveSystem
{
public:
// Добавить файл из которого будет происходить распаковка
static bqArchiveZipFile* ZipAdd(const char* zipFile);
// Распаковать файл. Если `a` NULL первый найденный файл будет распакован.
// Установи `a` если используешь много zip файлов которые содержат файлы с одинаковым именем.
// Функция выделит память используя bqMemory::malloc, и запишет размер в size
// Если всё OK то вернётся указатель.
// Используй bqMemory::free для освобождения памяти
static uint8_t* ZipUnzip(const char* fileInZip, uint32_t* size, bqArchiveZipFile* a = NULL);
// Установи все данные в `info`. Если сжатие было OK, вернётся true
// и m_dataCompressed будет иметь адрес
// Надо вызвать bqMemory::free(m_dataCompressed) для освобождения памяти
static bool Compress(bqCompressionInfo* info);
// Подобно методу `Compress`
// Выделяй память для m_dataUncompressed самостоятельно.
// Ты должен знать m_sizeUncompressed.
static bool Decompress(bqCompressionInfo* info);
// Расжать данные и сунуть всё в массив
// Если info.m_dataCompressed NULL (или !info.m_sizeCompressed)
// тогда функция попробует копировать данные из info.m_dataUncompressed
// Функция выделит память, вставит все данные в массив и освободит память.
// info.m_dataUncompressed и info.m_sizeUncompressed будут проигнорированы
static bool Decompress(bqCompressionInfo* info, bqArray<uint8_t>& toArray);
};
Всё же по уму надо делать своё чтение ZIP + добавить свой формат, схожий с zip.
Для того чтобы сжать буфер нужно указать библиотеку с алгоритмом. Реализовано fastlz.
bool bqArchiveSystem::Compress(bqCompressionInfo* info)
{
BQ_ASSERT_ST(info);
BQ_ASSERT_ST(info->m_dataUncompressed);
BQ_ASSERT_ST(info->m_sizeUncompressed);
info->m_dataCompressed = 0;
info->m_sizeCompressed = 0;
switch (info->m_compressorType)
{
case bqCompressorType::fastlz:
return g_framework->_compress_fastlz(info);
}
return false;
}
bool bqArchiveSystem::Decompress(bqCompressionInfo* info)
{
BQ_ASSERT_ST(info);
BQ_ASSERT_ST(info->m_dataCompressed);
BQ_ASSERT_ST(info->m_sizeCompressed);
BQ_ASSERT_ST(info->m_dataUncompressed);
BQ_ASSERT_ST(info->m_sizeUncompressed);
switch (info->m_compressorType)
{
case bqCompressorType::fastlz:
return g_framework->_decompress_fastlz(info);
}
return false;
}
bool bqArchiveSystem::Decompress(bqCompressionInfo* info, bqArray<uint8_t>& toVector)
{
BQ_ASSERT_ST(info);
toVector.clear();
toVector.reserve(info->m_sizeUncompressed);
if (!info->m_dataCompressed || !info->m_sizeCompressed)
{
if (info->m_dataUncompressed && info->m_sizeUncompressed)
{
// НАДО РЕАЛИЗОВАТЬ ЭТО ДЛЯ bqArray !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
std::copy(info->m_dataUncompressed,
info->m_dataUncompressed + info->m_sizeUncompressed,
std::back_inserter(toVector));
return true;
}
return false;
}
switch (info->m_compressorType)
{
case bqCompressorType::fastlz:
{
bqCompressionInfo cmpInf = *info;
cmpInf.m_dataUncompressed = (uint8_t*)bqMemory::malloc(cmpInf.m_sizeUncompressed);
bool result = g_framework->_decompress_fastlz(&cmpInf);
if (result)
{
// НАДО РЕАЛИЗОВАТЬ ЭТО ДЛЯ bqArray !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
std::copy(
cmpInf.m_dataUncompressed,
cmpInf.m_dataUncompressed + cmpInf.m_sizeUncompressed,
std::back_inserter(toVector));
}
bqMemory::free(cmpInf.m_dataUncompressed);
return result;
}break;
}
return false;
}
bool bqFrameworkImpl::_compress_fastlz(bqCompressionInfo* info)
{
if (info->m_level != 1 && info->m_level != 2)
info->m_level = 2;
if (info->m_sizeUncompressed < 32)
return false;
uint8_t* output = (uint8_t*)bqMemory::malloc(info->m_sizeUncompressed + (info->m_sizeUncompressed / 3));
int compressed_size = fastlz_compress_level(
(int)info->m_level,
info->m_dataUncompressed,
info->m_sizeUncompressed,
output);
if ((uint32_t)compressed_size >= info->m_sizeUncompressed)
{
bqMemory::free(output);
return false;
}
output = (uint8_t*)bqMemory::realloc(output, compressed_size);
info->m_dataCompressed = output;
info->m_sizeCompressed = compressed_size;
return true;
}
bool bqFrameworkImpl::_decompress_fastlz(bqCompressionInfo* info)
{
int decompressed_size = fastlz_decompress(
info->m_dataCompressed,
info->m_sizeCompressed,
info->m_dataUncompressed,
info->m_sizeUncompressed);
if (!decompressed_size)
{
bqLog::PrintWarning("%s: %s\n", __FUNCTION__, "can't decompress");
return false;
}
if (info->m_sizeUncompressed != decompressed_size)
{
info->m_sizeUncompressed = decompressed_size;
bqLog::PrintWarning("%s: %s\n", __FUNCTION__, "`decompressed size` is not the sme with m_sizeUncompressed");
}
return true;
}
ZipAdd добавит файл архива в список из которого нужно будет делать поиск при загрузке ресурса. Вернётся указатель на структуру. Можно не делать поиск, а сразу послать этот указатель, хотя зачем замарачиваться, обычно архивов так-то используется не много.
bqArchiveZipFile* bqArchiveSystem::ZipAdd(const char* fn)
{
BQ_ASSERT_ST(fn);
bqLog::Print("AddZip: [%s]\n", fn);
if (!std::filesystem::exists(fn))
{
bqLog::PrintWarning("AddZip: Unable to find zip file [%s]\n", fn);
return 0;
}
unzFile uf = unzOpen64(fn);
if (uf == NULL)
{
bqLog::PrintWarning("AddZip: unzOpen64 failed [%s]\n", fn);
return 0;
}
bqArchiveZipFile* zp = bqCreate<bqArchiveZipFile>();
zp->m_fileName.assign(fn);
zp->m_implementation = uf;
g_framework->m_zipFiles.push_back(zp);
return zp;
}
uint8_t* bqArchiveSystem::ZipUnzip(const char* fileInZip, uint32_t* size, bqArchiveZipFile* a)
{
BQ_ASSERT_ST(fileInZip);
BQ_ASSERT_ST(size);
if (a)
goto lockate_file;
for (size_t i = 0, sz = g_framework->m_zipFiles.size(); i < sz; ++i)
{
if (unzLocateFile(g_framework->m_zipFiles[i]->m_implementation, fileInZip, 0) == UNZ_OK)
{
a = g_framework->m_zipFiles[i];
goto unzip_file;
}
}
bqLog::PrintWarning("file %s not found in the zipfile\n", fileInZip);
return 0;
lockate_file:;
if (unzLocateFile(a->m_implementation, fileInZip, 0) != UNZ_OK)
{
bqLog::PrintWarning("file %s not found in the zipfile\n", fileInZip);
return 0;
}
unzip_file:;
char filename_inzip[256];
unz_file_info64 file_info;
int err = unzGetCurrentFileInfo64(a->m_implementation, &file_info,
filename_inzip,
sizeof(filename_inzip),
NULL, 0, NULL, 0);
if (err != UNZ_OK)
{
bqLog::PrintWarning("error %d with zipfile in unzGetCurrentFileInfo [%s]\n", err, fileInZip);
return 0;
}
err = unzOpenCurrentFilePassword(a->m_implementation, 0);
if (err != UNZ_OK)
{
bqLog::PrintWarning("error %d with zipfile in unzOpenCurrentFilePassword [%s]\n", err, fileInZip);
return 0;
}
uint8_t* data = (uint8_t*)bqMemory::malloc((size_t)file_info.uncompressed_size);
*size = (uint32_t)file_info.uncompressed_size;
err = unzReadCurrentFile(a->m_implementation, data, (unsigned int)file_info.uncompressed_size);
if (err < 0)
{
bqLog::PrintWarning("error %d with zipfile in unzReadCurrentFile [%s]\n", err, fileInZip);
free(data);
data = 0;
}
unzCloseCurrentFile(a->m_implementation);
return data;
}
Так как при загрузке BMP и OBJ я уже использую буфер (я не читаю файл напрямую), то ничего менять не надо, надо просто зайти в метод bqFramework::SummonFileBuffer
и в конце добавить
uint8_t* bqFramework::SummonFileBuffer(const char* path, uint32_t* szOut, bool isText)
{
BQ_ASSERT_ST(path);
BQ_ASSERT_ST(szOut);
*szOut = 0;
std::filesystem::path p = path;
if (std::filesystem::exists(p))
{
...
}
return bqArchiveSystem::ZipUnzip(path, szOut, 0);
}
Если файла нет, то будет попытка его распаковать из добавленных ранее архивов.
Массив с архивами лежит там bqFrameworkImpl
. И методы.
// Архивы, сжатие
bool _compress_fastlz(bqCompressionInfo* info);
bool _decompress_fastlz(bqCompressionInfo* info);
void _onDestroy_archive();
std::vector<bqArchiveZipFile*> m_zipFiles;
При завершении работы надо закрыть все архивы
void bqFrameworkImpl::_onDestroy_archive()
{
for (size_t i = 0; i < m_zipFiles.size(); ++i)
{
if (m_zipFiles[i]->m_implementation)
{
unzClose(m_zipFiles[i]->m_implementation);
}
bqDestroy(m_zipFiles[i]);
}
}
Все сторонние библиотеки скомпилированы в .lib файлы. Их подключение.
BQ_LINK_LIBRARY("minizip");
BQ_LINK_LIBRARY("fastlz");
BQ_LINK_LIBRARY("zlib");
BQ_LINK_LIBRARY("bzip2");
Путь к файлу надо указывать так как он лежит в архиве.
Например, в архиве лежит папка 'media' и в ней файлы, то при загрузке надо указать
media/file.ext
Если мы хотим чтобы загружался файл из папки, если он существует, а если нет то чтобы он распаковывался, надо делать преобразования.
Так как для загрузки из файла путь указывается относительно рабочей папки
../media/1.obj
то надо преобразовать эту строку в понятную для распаковки из ZIP
media/1.obj
Сначала надо знать где лежит программа.
wchar_t pth[1000];
GetModuleFileName(0, pth, 1000);
g_framework->m_appPath = pth;
g_framework->m_appPath.pop_back_before(U'\\');
g_framework->m_appPath.replace(U'\\', U'/');
Добавляется метод для получения этого пути
bqString bqFramework::GetAppPath()
{
return g_framework->m_appPath;
}
Далее преобразование пути
bqStringA bqFramework::GetPath(const bqString& v)
{
bqString p = g_framework->m_appPath;
p.append(v);
bqStringA stra;
p.to_utf8(stra);
if (!std::filesystem::exists(stra.c_str()))
{
p.assign(v);
while (p.size())
{
if (p[0] == U'.'
|| p[0] == U'\\'
|| p[0] == U'/')
p.pop_front();
else
break;
}
p.to_utf8(stra);
}
return stra;
}
Если файл найден, то идёт возврат пути типа
C:\code\app\bin32\..\media\1.obj
если не найден, то возвращается обрезанная версия.
media\1.obj
Поэтому, если хочется чтобы файл был и в архиве и в папке, и чтобы файл в папке был загружен, то надо делать так
bqImage* image = bqFramework::SummonImage(bqFramework::GetPath("../media/image.bmp").c_str());
Если же файла в папке не будет, будет распакован файл из архива. Главное не забыть добавить архив
например bqArchiveSystem::ZipAdd("../media/data.zip");
и чтобы пути совпадали (названия папок в архиве и на диске)
https://github.com/badcoiq/badcoiq/tree/faf1a44ae7d997bd90b7f0f0309a5566655f8060