From 9ae3b193c461b168336d1d9e272aa834705633d7 Mon Sep 17 00:00:00 2001 From: Aki Date: Sun, 7 Aug 2022 21:45:05 +0200 Subject: Reimplemented part of archive format This is getting reimplemented rather than refactor mostly in order to make clear which parts are needed for backwards compatiblity (reading-wise) and which are not. The current implementation has quite a number of quirks and potential failure points despite not being large. Understanding them is not worth it. --- ArchiveEx/Archive.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ ArchiveEx/Archive.h | 51 ++++++++++++++++++++++++++++++ ArchiveEx/CMakeLists.txt | 13 ++++++++ CMakeLists.txt | 1 + 4 files changed, 147 insertions(+) create mode 100644 ArchiveEx/Archive.cpp create mode 100644 ArchiveEx/Archive.h create mode 100644 ArchiveEx/CMakeLists.txt diff --git a/ArchiveEx/Archive.cpp b/ArchiveEx/Archive.cpp new file mode 100644 index 0000000..d714e46 --- /dev/null +++ b/ArchiveEx/Archive.cpp @@ -0,0 +1,82 @@ +#include "Archive.h" + +#include +#include +#include + +#include + + +namespace Archive +{ + + +using UniqueFileHandle = std::unique_ptr; + + +static constexpr std::size_t DIRECTORY_MARGIN {64}; + + +static std::size_t BytesToBlocks(std::size_t bytes); + + +Archive::Archive(const char* p) : + path {p}, + header {}, + entries {} +{ + UniqueFileHandle file {std::fopen(path, "rb"), &std::fclose}; + if (!file) + throw "could not open archive"; + std::size_t length = fread(&header, sizeof(Header), 1, file.get()); + if (1 != length) + throw "could not read"; + if (VERSION != header.version) + throw "bad version"; + int err = std::fseek(file.get(), sizeof(Header) + header.directory.offset, SEEK_SET); + if (-1 == err) + throw "could not find directory in archive"; + std::vector compressed(header.directory.compressed_size); + length = std::fread(compressed.data(), 1, header.directory.compressed_size, file.get()); + if (header.directory.compressed_size != length) + throw "could not read compressed directory"; + const std::size_t total_entries = header.total_entries + DIRECTORY_MARGIN; + entries.resize(header.total_entries); + entries.reserve(total_entries); // In original impl entries were switched on/off with their uncompressed size value. + uLongf uncompressed_size = sizeof(Entry) * total_entries; + err = ::uncompress( + reinterpret_cast(entries.data()), + &uncompressed_size, + compressed.data(), + header.directory.compressed_size); + if (Z_OK != err) + throw "could not uncompress directory"; +} + + +void +Archive::PrintNamesOfEntries() const +{ + for (const auto& entry : entries) + std::printf("%s\n", entry.name); +} + + +std::size_t +Archive::DirectoryBlocks() const +{ + const std::size_t blocks = BytesToBlocks(header.total_entries * sizeof(Entry)); + return blocks == 0 ? 1 : blocks; +} + + +std::size_t +BytesToBlocks(const std::size_t bytes) +{ + const std::size_t full = bytes / BLOCK_SIZE; + const std::size_t partial = (bytes % BLOCK_SIZE) > 0; + return full + partial; +} + + +} // namespace Archive diff --git a/ArchiveEx/Archive.h b/ArchiveEx/Archive.h new file mode 100644 index 0000000..cc06dc4 --- /dev/null +++ b/ArchiveEx/Archive.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + + +namespace Archive +{ + + +static constexpr std::uint32_t VERSION {0x0010}; +static constexpr std::size_t BLOCK_SIZE {1024}; +static constexpr std::size_t NAMELEN {64}; + + +struct Header +{ + std::uint32_t version; + std::uint32_t total_entries; + struct { + std::uint32_t total_blocks; + std::uint32_t compressed_size; + std::uint32_t offset; + } directory; +}; + + +struct Entry +{ + char name[NAMELEN]; + std::uint32_t original_size; + std::uint32_t compressed_size; + std::uint32_t offset; +}; + + +class Archive +{ +public: + explicit Archive(const char* path); + void PrintNamesOfEntries() const; + std::size_t DirectoryBlocks() const; +private: + const char* path; + Header header; + std::vector entries; +}; + + +} // namespace Archive diff --git a/ArchiveEx/CMakeLists.txt b/ArchiveEx/CMakeLists.txt new file mode 100644 index 0000000..b2a1948 --- /dev/null +++ b/ArchiveEx/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ArchiveEx) +add_library( + ArchiveEx + Archive.cpp + ) +target_include_directories( + ArchiveEx + PUBLIC . + ) +target_link_libraries( + ArchiveEx + PRIVATE Zlib::zlib + ) diff --git a/CMakeLists.txt b/CMakeLists.txt index fdb33d6..fea3356 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ if(MSVC) set(WINDOWSSDK_LIBPATH "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.19041.0" CACHE FILEPATH "Path to a versioned lib directory of selected Windows SDK") set(WINDOWSSDK_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0" CACHE FILEPATH "Path to a versioned include directory of selected Windows SDK") endif() +add_subdirectory(ArchiveEx) add_subdirectory(contrib) add_subdirectory(data) add_subdirectory(Datafile) -- cgit v1.1