File ZArchive-0.1.2+git20240721.b467f7a.obscpio of Package ZArchive
07070100000000000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000002B00000000ZArchive-0.1.2+git20240721.b467f7a/.github07070100000001000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000003500000000ZArchive-0.1.2+git20240721.b467f7a/.github/workflows07070100000002000081A4000000000000000000000001669D41C600001058000000000000000000000000000000000000003F00000000ZArchive-0.1.2+git20240721.b467f7a/.github/workflows/build.ymlname: Build ZArchive
on:
push:
branches: [ master ]
pull_request:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
fail-fast: false
# Set up a matrix to run the following 3 configurations:
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc, clang, cl]
include:
- os: windows-latest
c_compiler: cl
cpp_compiler: cl
- os: ubuntu-latest
c_compiler: gcc
cpp_compiler: g++
- os: ubuntu-latest
c_compiler: clang
cpp_compiler: clang++
exclude:
- os: windows-latest
c_compiler: gcc
- os: windows-latest
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Configure CMake (Linux)
if: matrix.os != 'windows-latest'
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake
-DBUILD_STATIC_TOOL=ON
-S ${{ github.workspace }}
- name: Configure CMake (Windows)
if: matrix.os == 'windows-latest'
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
-DBUILD_STATIC_TOOL=ON
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }}
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: zarchive-build-${{matrix.os}}-${{matrix.build_type}}-${{matrix.c_compiler}}
path: ${{ steps.strings.outputs.build-output-dir }}
07070100000003000081A4000000000000000000000001669D41C600000042000000000000000000000000000000000000002E00000000ZArchive-0.1.2+git20240721.b467f7a/.gitignorecmake-build-*/
.idea
# Visual Studio
.vs/
out/
CMakeSettings.json07070100000004000081A4000000000000000000000001669D41C600000B25000000000000000000000000000000000000003200000000ZArchive-0.1.2+git20240721.b467f7a/CMakeLists.txtcmake_minimum_required (VERSION 3.15)
project("ZArchive"
VERSION "0.1.3"
DESCRIPTION "Library for creating and reading zstd-compressed file archives"
HOMEPAGE_URL "https://github.com/Exzap/ZArchive"
)
if (WIN32)
option(BUILD_STATIC_TOOL "Build the standalone executable statically" ON)
elseif(UNIX)
option(BUILD_STATIC_TOOL "Build the standalone executable statically" OFF)
if (BUILD_STATIC_TOOL)
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
endif()
endif()
if (BUILD_STATIC_TOOL)
message(STATUS "Building standalone executable statically")
set(VCPKG_LIBRARY_LINKAGE "static" CACHE STRING "Vcpkg target triplet")
set(STATIC_TOOL_FLAG "-static")
else()
message(STATUS "Building standalone executable dynamically")
set(VCPKG_LIBRARY_LINKAGE "dynamic" CACHE STRING "Vcpkg target triplet")
set(STATIC_TOOL_FLAG "")
endif()
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(GNUInstallDirs)
set(SOURCE_FILES_LIB
src/zarchivewriter.cpp
src/zarchivereader.cpp
src/sha_256.c
)
# build static library
add_library (zarchive ${SOURCE_FILES_LIB})
add_library (ZArchive::zarchive ALIAS zarchive)
target_compile_features(zarchive PUBLIC cxx_std_20)
set_target_properties(zarchive PROPERTIES
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
)
target_include_directories(zarchive
PUBLIC
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)
find_package(zstd MODULE REQUIRED) # MODULE because zstd::zstd is not defined upstream
target_link_libraries(zarchive PRIVATE zstd::zstd ${STATIC_TOOL_FLAG})
# standalone executable
add_executable (zarchiveTool src/main.cpp)
set_property(TARGET zarchiveTool PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_target_properties(zarchiveTool PROPERTIES OUTPUT_NAME "zarchive")
target_link_libraries(zarchiveTool PRIVATE zarchive ${STATIC_TOOL_FLAG})
# install
install(DIRECTORY include/zarchive/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/zarchive" FILES_MATCHING PATTERN "zarchive*.h")
install(TARGETS zarchive)
install(TARGETS zarchiveTool)
# pkg-config
include(JoinPaths) # can be replaced by cmake_path(APPEND) in CMake 3.20
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
configure_file("zarchive.pc.in" "zarchive.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/zarchive.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
07070100000005000081A4000000000000000000000001669D41C600000383000000000000000000000000000000000000002B00000000ZArchive-0.1.2+git20240721.b467f7a/LICENSEMIT No Attribution
Copyright 2022 Exzap
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.07070100000006000081A4000000000000000000000001669D41C6000009ED000000000000000000000000000000000000002D00000000ZArchive-0.1.2+git20240721.b467f7a/README.md## Overview
ZArchive is yet another file archive format. Think of zip, tar, 7z, etc. but with the requirement of allowing random-access reads and supporting compression.
## Features / Specifications
- Supports random-access reads within stored files
- Uses zstd compression (64KiB blocks)
- Scales reasonably well up to multiple terabytes with millions of files
- The theoretical size limit per-file is 2^48-1 (256 Terabyte)
- The encoding for paths within the archive is Windows-1252 (case-insensitive)
- Contains a SHA256 hash of the whole archive for integrity checks
- Endian-independent. The format always uses big-endian internally
- Stateless file and directory iterator handles which don't require memory allocation
## Example - Basic read file
```c++
#include "zarchive/zarchivereader.h"
int main()
{
ZArchiveReader* reader = ZArchiveReader::OpenFromFile("archive.zar");
if (!reader)
return -1;
ZArchiveNodeHandle fileHandle = reader->LookUp("myfolder/example.bin");
if (reader->IsFile(fileHandle))
{
uint8_t buffer[1000];
uint64_t n = reader->ReadFromFile(fileHandle, 0, 1000, buffer);
// buffer now contains the first n (up to 1000) bytes of example.bin
}
delete reader;
return 0;
}
```
For a more detailed example see [main.cpp](/src/main.cpp)
## Limitations
- Not designed for adding, removing or modifying files after the archive has been created
## No-seek creation
When creating new archives only byte append operations are used. No file seeking is necessary. This makes it possible to create archives on storage which is write-once. It also simplifies streaming ZArchive creation over network.
## UTF8 paths
UTF8 for file and folder paths is theoretically supported as paths are just binary blobs. But the case-insensitive comparison only applies to latin letters (a-z).
## Wii U specifics
Originally this format was created to store Wii U games dumps. These use the file extension .wua (Wii U Archive) but are otherwise regular ZArchive files. To allow multiple Wii U titles to be stored inside a single archive, each title must be placed in a subfolder following the naming scheme: 16-digit titleId followed by \_v and then the version as decimal. For example: 0005000e10102000_v32
## License
The ZArchive library is licensed under [MIT No Attribution](https://github.com/Exzap/ZArchive/blob/master/LICENSE), with the exception of [sha_256.c](/src/sha_256.c) and [sha_256.h](/src/sha_256.h) which are public domain, see: [ https://github.com/amosnier/sha-2]( https://github.com/amosnier/sha-2).
07070100000007000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000002900000000ZArchive-0.1.2+git20240721.b467f7a/cmake07070100000008000081A4000000000000000000000001669D41C600000423000000000000000000000000000000000000003800000000ZArchive-0.1.2+git20240721.b467f7a/cmake/Findzstd.cmake# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
# SPDX-License-Identifier: ISC
include(FindPackageHandleStandardArgs)
find_package(zstd CONFIG QUIET)
if (zstd_FOUND)
# Use upstream zstdConfig.cmake if possible
if (NOT TARGET zstd::zstd)
if (TARGET zstd::libzstd_static)
set_target_properties(zstd::libzstd_static PROPERTIES IMPORTED_GLOBAL TRUE)
add_library(zstd::zstd ALIAS zstd::libzstd_static)
elseif (TARGET zstd::libzstd_shared)
set_target_properties(zstd::libzstd_shared PROPERTIES IMPORTED_GLOBAL TRUE)
add_library(zstd::zstd ALIAS zstd::libzstd_shared)
endif()
endif()
find_package_handle_standard_args(zstd CONFIG_MODE)
else()
# Fallback to pkg-config otherwise
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_search_module(libzstd IMPORTED_TARGET GLOBAL libzstd)
if (libzstd_FOUND)
add_library(zstd::zstd ALIAS PkgConfig::libzstd)
endif()
endif()
find_package_handle_standard_args(zstd
REQUIRED_VARS
libzstd_LINK_LIBRARIES
libzstd_FOUND
VERSION_VAR libzstd_VERSION
)
endif()
07070100000009000081A4000000000000000000000001669D41C600000331000000000000000000000000000000000000003900000000ZArchive-0.1.2+git20240721.b467f7a/cmake/JoinPaths.cmake# This module provides function for joining paths
# known from most languages
#
# SPDX-License-Identifier: (MIT OR CC0-1.0)
# Copyright 2020 Jan Tojnar
# https://github.com/jtojnar/cmake-snips
#
# Modelled after Python’s os.path.join
# https://docs.python.org/3.7/library/os.path.html#os.path.join
# Windows not supported
function(join_paths joined_path first_path_segment)
set(temp_path "${first_path_segment}")
foreach(current_segment IN LISTS ARGN)
if(NOT ("${current_segment}" STREQUAL ""))
if(IS_ABSOLUTE "${current_segment}")
set(temp_path "${current_segment}")
else()
set(temp_path "${temp_path}/${current_segment}")
endif()
endif()
endforeach()
set(${joined_path} "${temp_path}" PARENT_SCOPE)
endfunction()
0707010000000A000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000002B00000000ZArchive-0.1.2+git20240721.b467f7a/include0707010000000B000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000003400000000ZArchive-0.1.2+git20240721.b467f7a/include/zarchive0707010000000C000081A4000000000000000000000001669D41C600002E19000000000000000000000000000000000000004500000000ZArchive-0.1.2+git20240721.b467f7a/include/zarchive/zarchivecommon.h#pragma once
#include <string>
#include <string_view>
#include <cstring>
/* Determine endianness */
/* Original code by https://github.com/rofl0r */
#if (defined __BYTE_ORDER__) && (defined __ORDER_LITTLE_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define ENDIANNESS_LE 1
# define ENDIANNESS_BE 0
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define ENDIANNESS_LE 0
# define ENDIANNESS_BE 1
# endif
/* Try to derive from arch/compiler-specific macros */
#elif defined(_X86_) || defined(__x86_64__) || defined(__i386__) || \
defined(__i486__) || defined(__i586__) || defined(__i686__) || \
defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) || \
defined(__ARMEL__) || \
defined(__MSP430__) || \
(defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \
(defined(_LITTLE_ENDIAN) && _LITTLE_ENDIAN == 1) || \
defined(_M_ARM) || defined(_M_ARM64) || \
defined(_M_IX86) || defined(_M_AMD64) /* MSVC */
# define ENDIANNESS_LE 1
# define ENDIANNESS_BE 0
#elif defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) || \
defined(__MICROBLAZEEB__) || defined(__ARMEB__) || \
(defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \
(defined(_BIG_ENDIAN) && _BIG_ENDIAN == 1)
# define ENDIANNESS_LE 0
# define ENDIANNESS_BE 1
/* Try to get it from a header */
#else
# if defined(__linux) || defined(__HAIKU__)
# include <endian.h>
# elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
defined(__DragonFly__)
# include <sys/endian.h>
# elif defined(__APPLE__)
# include <machine/endian.h>
# endif
#endif
#ifndef ENDIANNESS_LE
# undef ENDIANNESS_BE
# if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN)
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ENDIANNESS_LE 1
# define ENDIANNESS_BE 0
# elif __BYTE_ORDER == __BIG_ENDIAN
# define ENDIANNESS_LE 0
# define ENDIANNESS_BE 1
# endif
# elif defined(BYTE_ORDER) && defined(LITTLE_ENDIAN)
# if BYTE_ORDER == LITTLE_ENDIAN
# define ENDIANNESS_LE 1
# define ENDIANNESS_BE 0
# elif BYTE_ORDER == BIG_ENDIAN
# define ENDIANNESS_LE 0
# define ENDIANNESS_BE 1
# endif
# endif
#endif
namespace _ZARCHIVE
{
inline constexpr size_t COMPRESSED_BLOCK_SIZE = 64 * 1024; // 64KiB
inline constexpr size_t ENTRIES_PER_OFFSETRECORD = 16; // must be aligned to two
template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>)
{
return ((((i >> (N * 8)) & (T)(uint8_t)(-1)) << ((sizeof(T) - 1 - N) * 8)) | ...);
}
template<class T, class U = std::make_unsigned_t<T>>
constexpr T bswap(T i)
{
return (T)bswap_impl<U>((U)i, std::make_index_sequence<sizeof(T)>{});
}
template<class T>
inline T _store(const T src)
{
#if ENDIANNESS_BE != 0
return src;
#else
return bswap<T>(src);
#endif
}
template<class T>
inline T _load(const T src)
{
#if ENDIANNESS_BE != 0
return src;
#else
return bswap<T>(src);
#endif
}
struct CompressionOffsetRecord
{
// for every Nth entry we store the full 64bit offset, the blocks in between calculate the offset from the size array
uint64_t baseOffset;
uint16_t size[ENTRIES_PER_OFFSETRECORD]; // compressed size - 1
static void Serialize(const CompressionOffsetRecord* input, size_t count, CompressionOffsetRecord* output)
{
while (count)
{
output->baseOffset = _store(input->baseOffset);
for (size_t i = 0; i < ENTRIES_PER_OFFSETRECORD; i++)
output->size[i] = _store(input->size[i]);
input++;
output++;
count--;
}
}
static void Deserialize(CompressionOffsetRecord* input, size_t count, CompressionOffsetRecord* output)
{
Serialize(input, count, output);
}
};
static_assert(std::is_standard_layout<CompressionOffsetRecord>::value);
static_assert(sizeof(CompressionOffsetRecord) == (8 + 2 * 16));
struct FileDirectoryEntry
{
uint32_t nameOffsetAndTypeFlag; // MSB is type. 0 -> directory, 1 -> file. Lower 31 bit are the offset into the node name table
union
{
// note: Current serializer/deserializer code assumes both record types have the same data layout (three uint32_t) which allows for skipping a type check
struct
{
uint32_t fileOffsetLow;
uint32_t fileSizeLow;
uint32_t fileOffsetAndSizeHigh; // upper 16 bits -> file size extension, lower 16 bits -> file offset extension
}fileRecord;
struct
{
uint32_t nodeStartIndex;
uint32_t count;
uint32_t _reserved;
}directoryRecord;
};
void SetTypeAndNameOffset(bool isFile, uint32_t nameOffset)
{
nameOffsetAndTypeFlag = 0;
if (isFile)
nameOffsetAndTypeFlag |= 0x80000000;
else
nameOffsetAndTypeFlag &= ~0x80000000;
nameOffsetAndTypeFlag |= (nameOffset & 0x7FFFFFFF);
}
uint32_t GetNameOffset() const
{
return nameOffsetAndTypeFlag & 0x7FFFFFFF;
}
uint64_t GetFileOffset() const
{
uint64_t fileOffset = fileRecord.fileOffsetLow;
fileOffset |= ((uint64_t)(fileRecord.fileOffsetAndSizeHigh & 0xFFFF) << 32);
return fileOffset;
}
uint64_t GetFileSize() const
{
uint64_t fileSize = fileRecord.fileSizeLow;
fileSize |= ((uint64_t)(fileRecord.fileOffsetAndSizeHigh & 0xFFFF0000) << 16);
return fileSize;
}
void SetFileOffset(uint64_t fileOffset)
{
fileRecord.fileOffsetLow = (uint32_t)fileOffset;
fileRecord.fileOffsetAndSizeHigh &= 0xFFFF0000;
fileRecord.fileOffsetAndSizeHigh |= ((uint32_t)(fileOffset >> 32) & 0xFFFF);
}
void SetFileSize(uint64_t fileSize)
{
fileRecord.fileSizeLow = (uint32_t)fileSize;
fileRecord.fileOffsetAndSizeHigh &= 0x0000FFFF;
fileRecord.fileOffsetAndSizeHigh |= ((uint32_t)(fileSize >> 16) & 0xFFFF0000);
}
bool IsFile() const
{
return (nameOffsetAndTypeFlag & 0x80000000) != 0;
}
static void Serialize(const FileDirectoryEntry* input, size_t count, FileDirectoryEntry* output)
{
// Optimized method where we exploit the fact that fileRecord and dirRecord have the same layout:
while (count)
{
output->nameOffsetAndTypeFlag = _store(input->nameOffsetAndTypeFlag);
output->fileRecord.fileOffsetLow = _store(input->fileRecord.fileOffsetLow);
output->fileRecord.fileSizeLow = _store(input->fileRecord.fileSizeLow);
output->fileRecord.fileOffsetAndSizeHigh = _store(input->fileRecord.fileOffsetAndSizeHigh);
input++;
output++;
count--;
}
/* Generic implementation:
while (count)
{
if (input->IsFile())
{
output->fileRecord.fileOffsetLow = _store(input->fileRecord.fileOffsetLow);
output->fileRecord.fileSizeLow = _store(input->fileRecord.fileSizeLow);
output->fileRecord.fileOffsetAndSizeHigh = _store(input->fileRecord.fileOffsetAndSizeHigh);
}
else
{
output->directoryRecord.nodeStartIndex = _store(input->directoryRecord.nodeStartIndex);
output->directoryRecord.count = _store(input->directoryRecord.count);
output->directoryRecord._reserved = _store(input->directoryRecord._reserved);
}
output->nameOffsetAndTypeFlag = _store(input->nameOffsetAndTypeFlag);
input++;
output++;
count--;
}
*/
}
static void Deserialize(FileDirectoryEntry* input, size_t count, FileDirectoryEntry* output)
{
Serialize(input, count, output);
}
};
static_assert(std::is_standard_layout<FileDirectoryEntry>::value);
static_assert(sizeof(FileDirectoryEntry) == 16);
struct Footer
{
static inline uint32_t kMagic = 0x169f52d6;
static inline uint32_t kVersion1 = 0x61bf3a01; // also acts as an extended magic
struct OffsetInfo
{
uint64_t offset;
uint64_t size;
bool IsWithinValidRange(uint64_t fileSize) const
{
return (offset + size) <= fileSize;
}
};
OffsetInfo sectionCompressedData;
OffsetInfo sectionOffsetRecords;
OffsetInfo sectionNames;
OffsetInfo sectionFileTree;
OffsetInfo sectionMetaDirectory;
OffsetInfo sectionMetaData;
uint8_t integrityHash[32];
uint64_t totalSize;
uint32_t version;
uint32_t magic;
static void Serialize(const Footer* input, Footer* output)
{
output->magic = _store(input->magic);
output->version = _store(input->version);
output->totalSize = _store(input->totalSize);
output->sectionCompressedData.offset = _store(input->sectionCompressedData.offset);
output->sectionCompressedData.size = _store(input->sectionCompressedData.size);
output->sectionOffsetRecords.offset = _store(input->sectionOffsetRecords.offset);
output->sectionOffsetRecords.size = _store(input->sectionOffsetRecords.size);
output->sectionNames.offset = _store(input->sectionNames.offset);
output->sectionNames.size = _store(input->sectionNames.size);
output->sectionFileTree.offset = _store(input->sectionFileTree.offset);
output->sectionFileTree.size = _store(input->sectionFileTree.size);
output->sectionMetaDirectory.offset = _store(input->sectionMetaDirectory.offset);
output->sectionMetaDirectory.size = _store(input->sectionMetaDirectory.size);
output->sectionMetaData.offset = _store(input->sectionMetaData.offset);
output->sectionMetaData.size = _store(input->sectionMetaData.size);
memcpy(output->integrityHash, input->integrityHash, 32);
}
static void Deserialize(Footer* input, Footer* output)
{
Serialize(input, output);
}
};
static_assert(sizeof(Footer) == (16 * 6 + 32 + 8 + 4 + 4));
static bool GetNextPathNode(std::string_view& pathParser, std::string_view& node)
{
// skip leading slashes
while (!pathParser.empty() && (pathParser.front() == '/' || pathParser.front() == '\\'))
pathParser.remove_prefix(1);
if (pathParser.empty())
return false;
// the next slash is the delimiter
size_t index = 0;
for (index = 0; index < pathParser.size(); index++)
{
if (pathParser[index] == '/' || pathParser[index] == '\\')
break;
}
node = std::basic_string_view<char>(pathParser.data(), index);
pathParser.remove_prefix(index);
return true;
}
static void SplitFilenameFromPath(std::string_view& pathInOut, std::string_view& filename)
{
if (pathInOut.empty())
{
filename = pathInOut;
return;
}
// scan backwards until the first slash, this is where the filename starts
// if there is no slash then stop at index zero
size_t index = pathInOut.size() - 1;
while (true)
{
if (pathInOut[index] == '/' || pathInOut[index] == '\\')
{
index++; // slash isn't part of the filename
break;
}
if (index == 0)
break;
index--;
}
filename = std::basic_string_view<char>(pathInOut.data() + index, pathInOut.size() - index);
pathInOut.remove_suffix(pathInOut.size() - index);
}
static bool CompareNodeNameBool(std::string_view n1, std::string_view n2)
{
if (n1.size() != n2.size())
return false;
for (size_t i = 0; i < n1.size(); i++)
{
char c1 = n1[i];
char c2 = n2[i];
if (c1 >= 'A' && c1 <= 'Z')
c1 -= ('A' - 'a');
if (c2 >= 'A' && c2 <= 'Z')
c2 -= ('A' - 'a');
if (c1 != c2)
return false;
}
return true;
}
static int CompareNodeName(std::string_view n1, std::string_view n2)
{
for (size_t i = 0; i < std::min(n1.size(), n2.size()); i++)
{
char c1 = n1[i];
char c2 = n2[i];
if (c1 >= 'A' && c1 <= 'Z')
c1 -= ('A' - 'a');
if (c2 >= 'A' && c2 <= 'Z')
c2 -= ('A' - 'a');
if (c1 != c2)
return (int)(uint8_t)c2 - (int)(uint8_t)c1;
}
if (n1.size() < n2.size())
return 1;
if (n1.size() > n2.size())
return -1;
return 0;
}
};
0707010000000D000081A4000000000000000000000001669D41C6000009E6000000000000000000000000000000000000004500000000ZArchive-0.1.2+git20240721.b467f7a/include/zarchive/zarchivereader.h#pragma once
#include <cstdint>
#include <vector>
#include <string_view>
#include <unordered_map>
#include <mutex>
#include <filesystem>
#include <fstream>
#include "zarchivecommon.h"
using ZArchiveNodeHandle = uint32_t;
static inline ZArchiveNodeHandle ZARCHIVE_INVALID_NODE = 0xFFFFFFFF;
class ZArchiveReader
{
public:
struct DirEntry
{
std::string_view name;
bool isFile;
bool isDirectory;
uint64_t size; // only valid for directories
};
static ZArchiveReader* OpenFromFile(const std::filesystem::path& path);
~ZArchiveReader();
ZArchiveNodeHandle LookUp(std::string_view path, bool allowFile = true, bool allowDirectory = true);
bool IsDirectory(ZArchiveNodeHandle nodeHandle) const;
bool IsFile(ZArchiveNodeHandle nodeHandle) const;
// directory operations
uint32_t GetDirEntryCount(ZArchiveNodeHandle nodeHandle) const;
bool GetDirEntry(ZArchiveNodeHandle nodeHandle, uint32_t index, DirEntry& dirEntry) const;
// file operations
uint64_t GetFileSize(ZArchiveNodeHandle nodeHandle);
uint64_t ReadFromFile(ZArchiveNodeHandle nodeHandle, uint64_t offset, uint64_t length, void* buffer);
private:
struct CacheBlock
{
uint8_t* data;
uint64_t blockIndex;
// linked-list for LRU
CacheBlock* prev;
CacheBlock* next;
};
std::mutex m_accessMutex;
std::vector<uint8_t> m_cacheDataBuffer;
std::vector<CacheBlock> m_cacheBlocks;
CacheBlock* m_lruChainFirst;
CacheBlock* m_lruChainLast;
std::unordered_map<uint64_t, CacheBlock*> m_blockLookup;
ZArchiveReader(std::ifstream&& file, std::vector<_ZARCHIVE::CompressionOffsetRecord>&& offsetRecords, std::vector<uint8_t>&& nameTable, std::vector<_ZARCHIVE::FileDirectoryEntry>&& fileTree, uint64_t compressedDataOffset, uint64_t compressedDataSize);
CacheBlock* GetCachedBlock(uint64_t blockIndex);
CacheBlock* RecycleLRUBlock(uint64_t newBlockIndex);
void MarkBlockAsMRU(CacheBlock* block);
void RegisterBlock(CacheBlock* block, uint64_t blockIndex);
void UnregisterBlock(CacheBlock* block);
bool LoadBlock(CacheBlock* block);
static std::string_view GetName(const std::vector<uint8_t>& nameTable, uint32_t nameOffset);
std::ifstream m_file;
std::vector<_ZARCHIVE::CompressionOffsetRecord> m_offsetRecords;
std::vector<uint8_t> m_nameTable;
std::vector<_ZARCHIVE::FileDirectoryEntry> m_fileTree;
uint64_t m_compressedDataOffset;
uint64_t m_compressedDataSize;
uint64_t m_blockCount;
std::vector<uint8_t> m_blockDecompressionBuffer;
};0707010000000E000081A4000000000000000000000001669D41C6000009D1000000000000000000000000000000000000004500000000ZArchive-0.1.2+git20240721.b467f7a/include/zarchive/zarchivewriter.h#pragma once
#include <cstdint>
#include <vector>
#include <string_view>
#include <unordered_map>
#include "zarchivecommon.h"
class ZArchiveWriter
{
struct PathNode
{
PathNode() : isFile(false), nameIndex(0xFFFFFFFF) {};
PathNode(bool isFile, uint32_t nameIndex) : isFile(isFile), nameIndex(nameIndex) {};
bool isFile;
uint32_t nameIndex; // index in m_nodeNames
std::vector<PathNode*> subnodes;
// file properties
uint64_t fileOffset{};
uint64_t fileSize{};
// directory properties
uint32_t nodeStartIndex{};
};
public:
typedef void(*CB_NewOutputFile)(const int32_t partIndex, void* ctx);
typedef void(*CB_WriteOutputData)(const void* data, size_t length, void* ctx);
ZArchiveWriter(CB_NewOutputFile cbNewOutputFile, CB_WriteOutputData cbWriteOutputData, void* ctx);
~ZArchiveWriter();
bool StartNewFile(const char* path); // creates a new virtual file and makes it active
void AppendData(const void* data, size_t size); // appends data to currently active file
bool MakeDir(const char* path, bool recursive = false);
void Finalize();
private:
PathNode* GetNodeByPath(PathNode* root, std::string_view path);
PathNode* FindSubnodeByName(PathNode* parent, std::string_view nodeName);
uint32_t CreateNameEntry(std::string_view name);
void OutputData(const void* data, size_t length);
uint64_t GetCurrentOutputOffset() const;
void StoreBlock(const uint8_t* uncompressedData);
void WriteOffsetRecords();
void WriteNameTable();
void WriteFileTree();
void WriteMetaData();
void WriteFooter();
private:
// callbacks
CB_NewOutputFile m_cbNewOutputFile;
CB_WriteOutputData m_cbWriteOutputData;
void* m_cbCtx;
// file tree
PathNode m_rootNode;
PathNode* m_currentFileNode{ nullptr };
std::vector<std::string> m_nodeNames;
std::vector<uint32_t> m_nodeNameOffsets;
std::unordered_map<std::string, uint32_t> m_nodeNameLookup;
// footer
_ZARCHIVE::Footer m_footer;
// writes and compression
std::vector<uint8_t> m_currentWriteBuffer;
std::vector<uint8_t> m_compressionBuffer;
uint64_t m_currentCompressedWriteIndex{ 0 }; // output file write index
uint64_t m_currentInputOffset{ 0 }; // current offset within uncompressed file data
// uncompressed-to-compressed offset records
uint64_t m_numWrittenOffsetRecords{ 0 };
std::vector<_ZARCHIVE::CompressionOffsetRecord> m_compressionOffsetRecord;
// hashing
struct Sha_256* m_mainShaCtx{};
uint8_t m_integritySha[32];
};0707010000000F000041ED000000000000000000000002669D41C600000000000000000000000000000000000000000000002700000000ZArchive-0.1.2+git20240721.b467f7a/src07070100000010000081A4000000000000000000000001669D41C600001D33000000000000000000000000000000000000003000000000ZArchive-0.1.2+git20240721.b467f7a/src/main.cpp#include "zarchive/zarchivewriter.h"
#include "zarchive/zarchivereader.h"
#include <vector>
#include <fstream>
#include <filesystem>
#include <cassert>
#include <optional>
#include <stdio.h>
namespace fs = std::filesystem;
void PrintHelp()
{
puts("Usage:\n");
puts("zarchive.exe input_path [output_path]");
puts("If input_path is a directory, then output_path will be the ZArchive output file path");
puts("If input_path is a ZArchive file path, then output_path will be the output directory");
puts("output_path is optional");
}
bool ExtractFile(ZArchiveReader* reader, std::string_view srcPath, const fs::path& path)
{
ZArchiveNodeHandle fileHandle = reader->LookUp(srcPath, true, false);
if (fileHandle == ZARCHIVE_INVALID_NODE)
{
puts("Unable to extract file:");
puts(std::string(srcPath).c_str());
return false;
}
std::vector<uint8_t> buffer;
buffer.resize(64 * 1024);
std::ofstream fileOut(path, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);
if (!fileOut.is_open())
{
puts("Unable to write file:");
puts(path.generic_string().c_str());
}
uint64_t readOffset = 0;
while (true)
{
uint64_t bytesRead = reader->ReadFromFile(fileHandle, readOffset, buffer.size(), buffer.data());
if (bytesRead == 0)
break;
fileOut.write((const char*)buffer.data(), bytesRead);
readOffset += bytesRead;
}
if (readOffset != reader->GetFileSize(fileHandle))
return false;
return true;
}
bool ExtractRecursive(ZArchiveReader* reader, std::string srcPath, fs::path outputDirectory)
{
ZArchiveNodeHandle dirHandle = reader->LookUp(srcPath, false, true);
if (dirHandle == ZARCHIVE_INVALID_NODE)
return false;
std::error_code ec;
fs::create_directories(outputDirectory);
uint32_t numEntries = reader->GetDirEntryCount(dirHandle);
for (uint32_t i = 0; i < numEntries; i++)
{
ZArchiveReader::DirEntry dirEntry;
if (!reader->GetDirEntry(dirHandle, i, dirEntry))
{
puts("Directory contains invalid node");
return false;
}
puts(std::string(srcPath).append("/").append(dirEntry.name).c_str());
if (dirEntry.isDirectory)
{
ExtractRecursive(reader, std::string(srcPath).append("/").append(dirEntry.name), outputDirectory / dirEntry.name);
}
else
{
// extract file
if (!ExtractFile(reader, std::string(srcPath).append("/").append(dirEntry.name), outputDirectory / dirEntry.name))
return false;
}
}
return true;
}
int Extract(fs::path inputFile, fs::path outputDirectory)
{
std::error_code ec;
if (!fs::exists(inputFile, ec))
{
puts("Unable to find archive file");
return -10;
}
ZArchiveReader* reader = ZArchiveReader::OpenFromFile(inputFile);
if (!reader)
{
puts("Failed to open ZArchive");
return -11;
}
bool r = ExtractRecursive(reader, "", outputDirectory);
if (!r)
{
puts("Extraction failed");
delete reader;
return -12;
}
delete reader;
return 0;
}
struct PackContext
{
fs::path outputFilePath;
std::ofstream currentOutputFile;
bool hasError{false};
};
void _pack_NewOutputFile(const int32_t partIndex, void* ctx)
{
PackContext* packContext = (PackContext*)ctx;
packContext->currentOutputFile = std::ofstream(packContext->outputFilePath, std::ios::binary);
if (!packContext->currentOutputFile.is_open())
{
printf("Failed to create output file: %s\n", packContext->outputFilePath.string().c_str());
packContext->hasError = true;
}
}
void _pack_WriteOutputData(const void* data, size_t length, void* ctx)
{
PackContext* packContext = (PackContext*)ctx;
packContext->currentOutputFile.write((const char*)data, length);
}
int Pack(fs::path inputDirectory, fs::path outputFile)
{
std::vector<uint8_t> buffer;
buffer.resize(64 * 1024);
std::error_code ec;
PackContext packContext;
packContext.outputFilePath = outputFile;
ZArchiveWriter zWriter(_pack_NewOutputFile, _pack_WriteOutputData, &packContext);
if (packContext.hasError)
return -16;
for (auto const& dirEntry : fs::recursive_directory_iterator(inputDirectory))
{
fs::path pathEntry = fs::relative(dirEntry.path(), inputDirectory, ec);
if (dirEntry.is_directory())
{
if (!zWriter.MakeDir(pathEntry.generic_string().c_str(), false))
{
printf("Failed to create directory %s\n", pathEntry.string().c_str());
return -13;
}
}
else if (dirEntry.is_regular_file())
{
if (dirEntry == outputFile) {
continue;
}
printf("Adding %s\n", pathEntry.string().c_str());
if (!zWriter.StartNewFile(pathEntry.generic_string().c_str()))
{
printf("Failed to create archive file %s\n", pathEntry.string().c_str());
return -14;
}
std::ifstream inputFile(inputDirectory / pathEntry, std::ios::binary);
if (!inputFile.is_open())
{
printf("Failed to open input file %s\n", pathEntry.string().c_str());
return -15;
}
while( true )
{
inputFile.read((char*)buffer.data(), buffer.size());
int32_t readBytes = (int32_t)inputFile.gcount();
if (readBytes <= 0)
break;
zWriter.AppendData(buffer.data(), readBytes);
}
}
if (packContext.hasError)
return -16;
}
zWriter.Finalize();
return 0;
}
int main(int argc, char* argv[])
{
if (argc <= 1)
{
PrintHelp();
return 0;
}
std::optional<std::string> strInput;
std::optional<std::string> strOutput;
for (int i = 1; i < argc; i++)
{
if (strInput)
{
if (strOutput)
{
puts("Too many paths specified");
return -1;
}
else
{
strOutput = argv[i];
}
}
else
{
strInput = argv[i];
}
}
if (strInput)
{
std::error_code ec;
fs::path p(*strInput);
if (fs::is_regular_file(p, ec))
{
// extract
fs::path outputDirectory;
if (!strOutput)
{
fs::path defaultOutputPath = p.parent_path() / (p.stem().filename().string().append("_extracted"));
outputDirectory = defaultOutputPath;
printf("Extracting to: %s\n", outputDirectory.generic_string().c_str());
}
else
outputDirectory = *strOutput;
if (fs::exists(outputDirectory, ec) && !fs::is_directory(outputDirectory, ec))
{
puts("The specified output path is not a valid directory");
return -3;
}
fs::create_directories(outputDirectory, ec);
if (!fs::exists(outputDirectory, ec))
{
puts("Failed to create output directory");
return -4;
}
return Extract(p, outputDirectory);
}
else if(fs::is_directory(p, ec))
{
// pack
fs::path outputFile;
if (!strOutput)
{
fs::path defaultOutputPath = p.parent_path() / (p.stem().filename().string().append(".zar"));
outputFile = defaultOutputPath;
printf("Outputting to: %s\n", outputFile.generic_string().c_str());
}
else
outputFile = *strOutput;
if ((fs::exists(outputFile, ec) && !fs::is_regular_file(outputFile, ec)))
{
puts("The specified output path is not a valid file");
return -10;
}
if ((fs::exists(outputFile, ec) && fs::is_regular_file(outputFile, ec)))
{
puts("The output file already exists");
return -11;
}
int r = Pack(p, outputFile);
if (r != 0)
{
// delete incomplete output file
fs::remove(outputFile, ec);
}
return r;
}
else
{
puts("Input path is not a valid file or directory");
return -1;
}
}
return 0;
}
07070100000011000081A4000000000000000000000001669D41C600001DD4000000000000000000000000000000000000003100000000ZArchive-0.1.2+git20240721.b467f7a/src/sha_256.c#include "sha_256.h"
#define TOTAL_LEN_LEN 8
/* Original code by amosnier. Tweaked for extra performance */
/* https://github.com/amosnier/sha-2 */
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
* When useful for clarification, portions of the pseudo-code are reproduced here too.
*/
/*
* @brief Rotate a 32-bit value by a number of bits to the right.
* @param value The value to be rotated.
* @param count The number of bits to rotate by.
* @return The rotated value.
*/
static inline uint32_t right_rot(uint32_t value, unsigned int count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here.
*/
return value >> count | value << (32 - count);
}
static const uint32_t _sha256_k[] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2 };
/*
* @brief Update a hash value under calculation with a new chunk of data.
* @param h Pointer to the first hash item, of a total of eight.
* @param p Pointer to the chunk data, which has a standard length.
*
* @note This is the SHA-256 work horse.
*/
static inline void consume_chunk(uint32_t *h, const uint8_t *p)
{
unsigned i, j;
uint32_t ah[8];
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/*
* The w-array is really w[64], but since we only need 16 of them at a time, we save stack by
* calculating 16 at a time.
*
* This optimization was not there initially and the rest of the comments about w[64] are kept in their
* initial state.
*/
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63]
* don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the
* message schedule array
*/
uint32_t w[16];
const uint32_t* k = _sha256_k;
/* Unrolled i == 0 */
for (j = 0; j < 16; j++) {
w[j] = (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];
p += 4;
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
const uint32_t temp1 = ah[7] + s1 + ch + *k + w[j]; k++;
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
/* Compression function main loop: */
for (i = 1; i < 4; i++) {
for (j = 0; j < 16; j++) {
uint32_t t = w[(j + 1) & 0xf];
const uint32_t s0t = right_rot(t, 7) ^ right_rot(t, 18) ^ (t >> 3);
t = w[(j + 14) & 0xf];
const uint32_t s1t = right_rot(t, 17) ^ right_rot(t, 19) ^ (t >> 10);
w[j] = w[j] + s0t + w[(j + 9) & 0xf] + s1t;
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
const uint32_t temp1 = ah[7] + s1 + ch + *k + w[j]; k++;
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/*
* Public functions. See header file for documentation.
*/
void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH])
{
sha_256->hash = hash;
sha_256->chunk_pos = sha_256->chunk;
sha_256->space_left = SIZE_OF_SHA_256_CHUNK;
sha_256->total_len = 0;
/*
* Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes
* 2..19):
*/
sha_256->h[0] = 0x6a09e667;
sha_256->h[1] = 0xbb67ae85;
sha_256->h[2] = 0x3c6ef372;
sha_256->h[3] = 0xa54ff53a;
sha_256->h[4] = 0x510e527f;
sha_256->h[5] = 0x9b05688c;
sha_256->h[6] = 0x1f83d9ab;
sha_256->h[7] = 0x5be0cd19;
}
void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len)
{
sha_256->total_len += len;
const uint8_t *p = data;
while (len > 0) {
/*
* If the input chunks have sizes that are multiples of the calculation chunk size, no copies are
* necessary. We operate directly on the input data instead.
*/
if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) {
consume_chunk(sha_256->h, p);
len -= SIZE_OF_SHA_256_CHUNK;
p += SIZE_OF_SHA_256_CHUNK;
continue;
}
/* General case, no particular optimization. */
const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left;
memcpy(sha_256->chunk_pos, p, consumed_len);
sha_256->space_left -= consumed_len;
len -= consumed_len;
p += consumed_len;
if (sha_256->space_left == 0) {
consume_chunk(sha_256->h, sha_256->chunk);
sha_256->chunk_pos = sha_256->chunk;
sha_256->space_left = SIZE_OF_SHA_256_CHUNK;
} else {
sha_256->chunk_pos += consumed_len;
}
}
}
uint8_t *sha_256_close(struct Sha_256 *sha_256)
{
uint8_t *pos = sha_256->chunk_pos;
size_t space_left = sha_256->space_left;
uint32_t *const h = sha_256->h;
/*
* The current chunk cannot be full. Otherwise, it would already have be consumed. I.e. there is space left for
* at least one byte. The next step in the calculation is to add a single one-bit to the data.
*/
*pos++ = 0x80;
--space_left;
/*
* Now, the last step is to add the total data length at the end of the last chunk, and zero padding before
* that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add
* an extra chunk at the end.
*/
if (space_left < TOTAL_LEN_LEN) {
memset(pos, 0x00, space_left);
consume_chunk(h, sha_256->chunk);
pos = sha_256->chunk;
space_left = SIZE_OF_SHA_256_CHUNK;
}
const size_t left = space_left - TOTAL_LEN_LEN;
memset(pos, 0x00, left);
pos += left;
size_t len = sha_256->total_len;
pos[7] = (uint8_t)(len << 3);
len >>= 5;
int i;
for (i = 6; i >= 0; --i) {
pos[i] = (uint8_t)len;
len >>= 8;
}
consume_chunk(h, sha_256->chunk);
/* Produce the final hash value (big-endian): */
int j;
uint8_t *const hash = sha_256->hash;
for (i = 0, j = 0; i < 8; i++) {
hash[j++] = (uint8_t)(h[i] >> 24);
hash[j++] = (uint8_t)(h[i] >> 16);
hash[j++] = (uint8_t)(h[i] >> 8);
hash[j++] = (uint8_t)h[i];
}
return sha_256->hash;
}
void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len)
{
struct Sha_256 sha_256;
sha_256_init(&sha_256, hash);
sha_256_write(&sha_256, input, len);
(void)sha_256_close(&sha_256);
}
07070100000012000081A4000000000000000000000001669D41C600001060000000000000000000000000000000000000003100000000ZArchive-0.1.2+git20240721.b467f7a/src/sha_256.h#ifndef SHA_256_H
#define SHA_256_H
#include <stdint.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* @brief Size of the SHA-256 sum. This times eight is 256 bits.
*/
#define SIZE_OF_SHA_256_HASH 32
/*
* @brief Size of the chunks used for the calculations.
*
* @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for
* performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM!
*/
#define SIZE_OF_SHA_256_CHUNK 64
/*
* @brief The opaque SHA-256 type, that should be instantiated when using the streaming API.
*
* @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly
* accessing the fields, as they may change in the future.
*/
struct Sha_256 {
uint8_t *hash;
uint8_t chunk[SIZE_OF_SHA_256_CHUNK];
uint8_t *chunk_pos;
size_t space_left;
size_t total_len;
uint32_t h[8];
};
/*
* @brief The simple SHA-256 calculation function.
* @param hash Hash array, where the result is delivered.
* @param input Pointer to the data the hash shall be calculated on.
* @param len Length of the input data, in byte.
*
* @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is
* the function you should use.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len);
/*
* @brief Initialize a SHA-256 streaming calculation.
* @param sha_256 A pointer to a SHA-256 structure.
* @param hash Hash array, where the result will be delivered.
*
* @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is
* where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash
* buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256
* structure can be initialized again for the next calculation.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]);
/*
* @brief Stream more input data for an on-going SHA-256 calculation.
* @param sha_256 A pointer to a previously initialized SHA-256 structure.
* @param data Pointer to the data to be added to the calculation.
* @param len Length of the data to add, in byte.
*
* @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum
* data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times
* eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are
* unpredictable if that limit is exceeded.
*
* @note This function may be invoked on empty data (zero length), although that obviously will not add any data.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len);
/*
* @brief Conclude a SHA-256 streaming calculation, making the hash value available.
* @param sha_256 A pointer to a previously initialized SHA-256 structure.
* @return Pointer to the hash array, where the result is delivered.
*
* @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A
* pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer
* to the first byte of your initially provided hash array.
*
* @note If the passed pointer is NULL, the results are unpredictable.
*
* @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only
* has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string.
*/
uint8_t *sha_256_close(struct Sha_256 *sha_256);
#ifdef __cplusplus
}
#endif
#endif
07070100000013000081A4000000000000000000000001669D41C600003368000000000000000000000000000000000000003A00000000ZArchive-0.1.2+git20240721.b467f7a/src/zarchivereader.cpp#include "zarchive/zarchivereader.h"
#include "zarchive/zarchivecommon.h"
#include <fstream>
#include <zstd.h>
#include <cassert>
static uint64_t _ifstream_getFileSize(std::ifstream& file)
{
file.seekg(0, std::ios_base::end);
return (uint64_t)file.tellg();
}
static bool _ifstream_readBytes(std::ifstream& file, uint64_t offset, void* buffer, uint32_t size)
{
file.seekg(offset, std::ios_base::beg);
file.read((char*)buffer, size);
return file.gcount() == size;
}
static uint64_t _getValidElementCount(uint64_t size, uint64_t elementSize)
{
if ((size % elementSize) != 0)
return 0;
return size / elementSize;
}
ZArchiveReader* ZArchiveReader::OpenFromFile(const std::filesystem::path& path)
{
std::ifstream file;
file.open(path, std::ios_base::in | std::ios_base::binary);
if (!file.is_open())
return nullptr;
uint64_t fileSize = _ifstream_getFileSize(file);
if (fileSize <= sizeof(_ZARCHIVE::Footer))
return nullptr;
// read footer
_ZARCHIVE::Footer footer;
if (!_ifstream_readBytes(file, fileSize - sizeof(_ZARCHIVE::Footer), &footer, sizeof(_ZARCHIVE::Footer)))
return nullptr;
_ZARCHIVE::Footer::Deserialize(&footer, &footer);
// validate footer
if (footer.magic != _ZARCHIVE::Footer::kMagic)
return nullptr;
if (footer.version != _ZARCHIVE::Footer::kVersion1)
return nullptr;
if (footer.totalSize != fileSize)
return nullptr;
if (!footer.sectionCompressedData.IsWithinValidRange(fileSize) ||
!footer.sectionOffsetRecords.IsWithinValidRange(fileSize) ||
!footer.sectionNames.IsWithinValidRange(fileSize) ||
!footer.sectionFileTree.IsWithinValidRange(fileSize) ||
!footer.sectionMetaDirectory.IsWithinValidRange(fileSize) ||
!footer.sectionMetaData.IsWithinValidRange(fileSize))
return nullptr;
if (footer.sectionOffsetRecords.size > (uint64_t)0xFFFFFFFF)
return nullptr;
if (footer.sectionNames.size > (uint64_t)0x7FFFFFFF)
return nullptr;
if (footer.sectionFileTree.size > (uint64_t)0xFFFFFFFF)
return nullptr;
// read offset records
std::vector<_ZARCHIVE::CompressionOffsetRecord> offsetRecords;
offsetRecords.resize(_getValidElementCount(footer.sectionOffsetRecords.size, sizeof(_ZARCHIVE::CompressionOffsetRecord)));
if (offsetRecords.empty() || !_ifstream_readBytes(file, footer.sectionOffsetRecords.offset, offsetRecords.data(), (uint32_t)(offsetRecords.size() * sizeof(_ZARCHIVE::CompressionOffsetRecord))))
return nullptr;
_ZARCHIVE::CompressionOffsetRecord::Deserialize(offsetRecords.data(), offsetRecords.size(), offsetRecords.data());
// read name table
std::vector<uint8_t> nameTable;
nameTable.resize(footer.sectionNames.size);
if (!_ifstream_readBytes(file, footer.sectionNames.offset, nameTable.data(), (uint32_t)(nameTable.size() * sizeof(uint8_t))))
return nullptr;
// read file tree
std::vector<_ZARCHIVE::FileDirectoryEntry> fileTree;
fileTree.resize(_getValidElementCount(footer.sectionFileTree.size, sizeof(_ZARCHIVE::FileDirectoryEntry)));
if (fileTree.empty() || !_ifstream_readBytes(file, footer.sectionFileTree.offset, fileTree.data(), (uint32_t)(fileTree.size() * sizeof(_ZARCHIVE::FileDirectoryEntry))))
return nullptr;
_ZARCHIVE::FileDirectoryEntry::Deserialize(fileTree.data(), fileTree.size(), fileTree.data());
// verify file tree
if (fileTree[0].IsFile())
return nullptr; // first entry must be root directory
auto rootName = GetName(nameTable, fileTree[0].GetNameOffset());
if (!rootName.empty())
return nullptr; // root node must not have a name
// read meta data
// todo
ZArchiveReader* cfs = new ZArchiveReader(std::move(file), std::move(offsetRecords), std::move(nameTable), std::move(fileTree), footer.sectionCompressedData.offset, footer.sectionCompressedData.size);
return cfs;
}
ZArchiveReader::ZArchiveReader(std::ifstream&& file, std::vector<_ZARCHIVE::CompressionOffsetRecord>&& offsetRecords, std::vector<uint8_t>&& nameTable, std::vector<_ZARCHIVE::FileDirectoryEntry>&& fileTree, uint64_t compressedDataOffset, uint64_t compressedDataSize) :
m_file(std::move(file)), m_offsetRecords(std::move(offsetRecords)), m_nameTable(std::move(nameTable)), m_fileTree(std::move(fileTree)),
m_compressedDataOffset(compressedDataOffset), m_compressedDataSize(compressedDataSize)
{
m_blockCount = (uint64_t)m_offsetRecords.size() * _ZARCHIVE::ENTRIES_PER_OFFSETRECORD;
m_blockDecompressionBuffer.resize(_ZARCHIVE::COMPRESSED_BLOCK_SIZE);
// init cache
uint64_t cacheSize = 1024 * 1024 * 4; // 4MiB
if ((cacheSize % _ZARCHIVE::COMPRESSED_BLOCK_SIZE) != 0)
cacheSize += (_ZARCHIVE::COMPRESSED_BLOCK_SIZE - (cacheSize % _ZARCHIVE::COMPRESSED_BLOCK_SIZE));
m_cacheDataBuffer.resize(cacheSize);
// create cache blocks and init LRU chain
m_cacheBlocks.resize(cacheSize / _ZARCHIVE::COMPRESSED_BLOCK_SIZE);
m_lruChainFirst = m_cacheBlocks.data() + 0;
m_lruChainLast = m_cacheBlocks.data() + m_cacheBlocks.size() - 1;
CacheBlock* prevBlock = nullptr;
for (size_t i = 0; i < m_cacheBlocks.size(); i++)
{
m_cacheBlocks[i].blockIndex = 0xFFFFFFFFFFFFFFFF;
m_cacheBlocks[i].data = m_cacheDataBuffer.data() + i * _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
m_cacheBlocks[i].prev = prevBlock;
m_cacheBlocks[i].next = m_cacheBlocks.data() + i + 1;
prevBlock = m_cacheBlocks.data() + i;
}
m_cacheBlocks.back().next = nullptr;
}
ZArchiveReader::~ZArchiveReader()
{
}
ZArchiveNodeHandle ZArchiveReader::LookUp(std::string_view path, bool allowFile, bool allowDirectory)
{
std::string_view pathParser = path;
uint32_t currentNode = 0;
while (true)
{
std::string_view pathNodeName;
if (!_ZARCHIVE::GetNextPathNode(pathParser, pathNodeName))
return (ZArchiveNodeHandle)currentNode; // end of path reached
_ZARCHIVE::FileDirectoryEntry& entry = m_fileTree.at(currentNode);
if (entry.IsFile())
return ZARCHIVE_INVALID_NODE; // trying to iterate a file
// linear scan
// todo - we could accelerate this if we use binary search
uint32_t currentIndex = entry.directoryRecord.nodeStartIndex;
uint32_t endIndex = entry.directoryRecord.nodeStartIndex + entry.directoryRecord.count;
_ZARCHIVE::FileDirectoryEntry* match = nullptr;
while (currentIndex < endIndex)
{
_ZARCHIVE::FileDirectoryEntry& it = m_fileTree.at(currentIndex);
std::string_view itName = GetName(m_nameTable, it.GetNameOffset());
if (_ZARCHIVE::CompareNodeNameBool(pathNodeName, itName))
{
match = ⁢
break;
}
currentIndex++;
continue;
}
if (!match)
return ZARCHIVE_INVALID_NODE; // path not found
currentNode = (uint32_t)(match - m_fileTree.data());
}
return ZARCHIVE_INVALID_NODE;
}
bool ZArchiveReader::IsDirectory(ZArchiveNodeHandle nodeHandle) const
{
if (nodeHandle >= m_fileTree.size())
return false;
return !m_fileTree[nodeHandle].IsFile();
}
bool ZArchiveReader::IsFile(ZArchiveNodeHandle nodeHandle) const
{
if (nodeHandle >= m_fileTree.size())
return false;
return m_fileTree[nodeHandle].IsFile();
}
uint32_t ZArchiveReader::GetDirEntryCount(ZArchiveNodeHandle nodeHandle) const
{
if (nodeHandle >= m_fileTree.size())
return 0;
auto& entry = m_fileTree.at(nodeHandle);
if (entry.IsFile())
return 0;
return entry.directoryRecord.count;
}
bool ZArchiveReader::GetDirEntry(ZArchiveNodeHandle nodeHandle, uint32_t index, DirEntry& dirEntry) const
{
if (nodeHandle >= m_fileTree.size())
return false;
auto& dir = m_fileTree.at(nodeHandle);
if (dir.IsFile())
return false;
if (index >= dir.directoryRecord.count)
return false;
auto& it = m_fileTree.at(dir.directoryRecord.nodeStartIndex + index);
dirEntry.isFile = it.IsFile();
dirEntry.isDirectory = !dirEntry.isFile;
if (dirEntry.isFile)
dirEntry.size = it.GetFileSize();
else
dirEntry.size = 0;
dirEntry.name = GetName(m_nameTable, it.GetNameOffset());
if (dirEntry.name.empty())
return false; // bad name
return true;
}
uint64_t ZArchiveReader::GetFileSize(ZArchiveNodeHandle nodeHandle)
{
if (nodeHandle >= m_fileTree.size())
return 0;
auto& file = m_fileTree.at(nodeHandle);
if (!file.IsFile())
return 0;
return file.GetFileSize();
}
uint64_t ZArchiveReader::ReadFromFile(ZArchiveNodeHandle nodeHandle, uint64_t offset, uint64_t length, void* buffer)
{
if (nodeHandle >= m_fileTree.size())
return 0;
std::unique_lock<std::mutex> _lock(m_accessMutex);
auto& file = m_fileTree.at(nodeHandle);
if (!file.IsFile())
return 0;
uint64_t fileOffset = file.GetFileOffset();
uint64_t fileSize = file.GetFileSize();
if (offset >= fileSize)
return 0;
uint64_t bytesToRead = std::min(length, (fileSize - offset));
uint64_t rawReadOffset = fileOffset + offset;
uint64_t remainingBytes = bytesToRead;
uint8_t* bufferU8 = (uint8_t*)buffer;
while (remainingBytes > 0)
{
uint64_t blockIdx = rawReadOffset / _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
uint32_t blockOffset = (uint32_t)(rawReadOffset % _ZARCHIVE::COMPRESSED_BLOCK_SIZE);
uint32_t stepSize = std::min<uint32_t>(remainingBytes, _ZARCHIVE::COMPRESSED_BLOCK_SIZE - blockOffset);
CacheBlock* block = GetCachedBlock(blockIdx);
if (!block)
return 0;
std::memcpy(bufferU8, block->data + blockOffset, stepSize);
rawReadOffset += stepSize;
remainingBytes -= stepSize;
bufferU8 += stepSize;
}
return bytesToRead;
}
ZArchiveReader::CacheBlock* ZArchiveReader::GetCachedBlock(uint64_t blockIndex)
{
auto it = m_blockLookup.find(blockIndex);
if (it != m_blockLookup.end())
{
MarkBlockAsMRU(it->second);
return it->second;
}
if (blockIndex >= m_blockCount)
return nullptr;
// not in cache
CacheBlock* newBlock = RecycleLRUBlock(blockIndex);
if (!LoadBlock(newBlock))
{
UnregisterBlock(newBlock);
return nullptr;
}
return newBlock;
}
ZArchiveReader::CacheBlock* ZArchiveReader::RecycleLRUBlock(uint64_t newBlockIndex)
{
CacheBlock* recycledBlock = m_lruChainFirst;
UnregisterBlock(recycledBlock);
RegisterBlock(recycledBlock, newBlockIndex);
MarkBlockAsMRU(recycledBlock);
return recycledBlock;
}
void ZArchiveReader::MarkBlockAsMRU(ZArchiveReader::CacheBlock* block)
{
if (!block->next)
return; // already at the end of the list (MRU)
// remove from linked list
if (!block->prev)
{
m_lruChainFirst = block->next;
block->next->prev = nullptr;
}
else if (!block->next)
{
m_lruChainLast->next = block;
m_lruChainLast = block;
}
else
{
block->prev->next = block->next;
block->next->prev = block->prev;
}
// attach at the end
block->prev = m_lruChainLast;
block->next = nullptr;
m_lruChainLast->next = block;
m_lruChainLast = block;
}
void ZArchiveReader::RegisterBlock(CacheBlock* block, uint64_t blockIndex)
{
block->blockIndex = blockIndex;
m_blockLookup.emplace(blockIndex, block);
}
void ZArchiveReader::UnregisterBlock(CacheBlock* block)
{
if (block->blockIndex != 0xFFFFFFFFFFFFFFFF)
m_blockLookup.erase(block->blockIndex);
block->blockIndex = 0xFFFFFFFFFFFFFFFF;
}
bool ZArchiveReader::LoadBlock(CacheBlock* block)
{
uint32_t recordIndex = (uint32_t)(block->blockIndex / _ZARCHIVE::ENTRIES_PER_OFFSETRECORD);
uint32_t recordSubIndex = (uint32_t)(block->blockIndex % _ZARCHIVE::ENTRIES_PER_OFFSETRECORD);
if (recordIndex >= m_offsetRecords.size())
return false;
// determine offset and size of compressed block
auto& record = m_offsetRecords[recordIndex];
uint64_t offset = record.baseOffset;
for (uint32_t i = 0; i < recordSubIndex; i++)
{
offset += (uint64_t)record.size[i];
offset++;
}
uint32_t compressedSize = (uint32_t)record.size[recordSubIndex] + 1;
// load file data
if ((offset + compressedSize) > m_compressedDataSize)
return false;
offset += m_compressedDataOffset;
if (compressedSize == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
{
// uncompressed block, read directly into cached block
return _ifstream_readBytes(m_file, offset, block->data, compressedSize);
}
if (!_ifstream_readBytes(m_file, offset, m_blockDecompressionBuffer.data(), compressedSize))
return false;
// decompress
size_t outputSize = ZSTD_decompress(block->data, _ZARCHIVE::COMPRESSED_BLOCK_SIZE, m_blockDecompressionBuffer.data(), compressedSize);
return outputSize == _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
}
// returns empty view on failure
std::string_view ZArchiveReader::GetName(const std::vector<uint8_t>& nameTable, uint32_t nameOffset)
{
if (nameOffset == 0x7FFFFFFF || nameOffset > nameTable.size())
return "";
// parse header
uint16_t nameLength = nameTable[nameOffset] & 0x7F;
if (nameTable[nameOffset] & 0x80)
{
// extended 2-byte length
if (nameOffset + 1 >= nameTable.size())
return "";
nameLength |= ((uint16_t)nameTable[nameOffset] << 7);
nameOffset += 2;
}
else
nameOffset++;
// nameOffset can never exceed 0x7FFFFFFF so we don't have to worry about an overflow here
if ((nameOffset + (uint32_t)nameLength) > nameTable.size())
return "";
return std::basic_string_view<char>((char*)nameTable.data() + nameOffset, nameLength);
}07070100000014000081A4000000000000000000000001669D41C600002C9D000000000000000000000000000000000000003A00000000ZArchive-0.1.2+git20240721.b467f7a/src/zarchivewriter.cpp#include "zarchive/zarchivewriter.h"
#include "zarchive/zarchivecommon.h"
#include <string>
#include <string_view>
#include <queue>
#include <zstd.h>
#include "sha_256.h"
#include <cassert>
#include <algorithm>
ZArchiveWriter::ZArchiveWriter(CB_NewOutputFile cbNewOutputFile, CB_WriteOutputData cbWriteOutputData, void* ctx) : m_cbCtx(ctx), m_cbNewOutputFile(cbNewOutputFile), m_cbWriteOutputData(cbWriteOutputData)
{
cbNewOutputFile(-1, ctx);
m_mainShaCtx = (struct Sha_256*)malloc(sizeof(struct Sha_256));
sha_256_init(m_mainShaCtx, m_integritySha);
};
ZArchiveWriter::~ZArchiveWriter()
{
free(m_mainShaCtx);
}
ZArchiveWriter::PathNode* ZArchiveWriter::GetNodeByPath(ZArchiveWriter::PathNode* root, std::string_view path)
{
PathNode* currentNode = &m_rootNode;
std::string_view pathParser = path;
while (true)
{
std::string_view nodeName;
if (!_ZARCHIVE::GetNextPathNode(pathParser, nodeName))
break;
PathNode* nextSubnode = FindSubnodeByName(currentNode, nodeName);
if (!nextSubnode || (nextSubnode && nextSubnode->isFile))
return nullptr;
currentNode = nextSubnode;
}
return currentNode;
}
ZArchiveWriter::PathNode* ZArchiveWriter::FindSubnodeByName(ZArchiveWriter::PathNode* parent, std::string_view nodeName)
{
for (auto& it : parent->subnodes)
{
std::string_view itName = m_nodeNames[it->nameIndex];
if (_ZARCHIVE::CompareNodeNameBool(itName, nodeName))
return it;
}
return nullptr;
}
bool ZArchiveWriter::StartNewFile(const char* path)
{
m_currentFileNode = nullptr;
std::string_view pathParser = path;
std::string_view filename;
_ZARCHIVE::SplitFilenameFromPath(pathParser, filename);
PathNode* dir = GetNodeByPath(&m_rootNode, pathParser);
if (!dir)
return false;
if (FindSubnodeByName(dir, filename))
return false;
// add new entry and make it the currently active file for append operations
PathNode*& r = dir->subnodes.emplace_back(new PathNode(true, CreateNameEntry(filename)));
m_currentFileNode = r;
r->fileOffset = m_currentInputOffset;
return true;
}
bool ZArchiveWriter::MakeDir(const char* path, bool recursive)
{
std::string_view pathParser = path;
while (!pathParser.empty() && (pathParser.back() == '/' || pathParser.back() == '\\'))
pathParser.remove_suffix(1);
if (!recursive)
{
std::string_view dirName;
_ZARCHIVE::SplitFilenameFromPath(pathParser, dirName);
PathNode* dir = GetNodeByPath(&m_rootNode, pathParser);
if (!dir)
return false;
if (FindSubnodeByName(dir, dirName))
return false;
dir->subnodes.emplace_back(new PathNode(false, CreateNameEntry(dirName)));
}
else
{
PathNode* currentNode = &m_rootNode;
while (true)
{
std::string_view nodeName;
if (!_ZARCHIVE::GetNextPathNode(pathParser, nodeName))
break;
PathNode* nextSubnode = FindSubnodeByName(currentNode, nodeName);
if (nextSubnode && nextSubnode->isFile)
return false;
if (!nextSubnode)
{
PathNode*& r = currentNode->subnodes.emplace_back(new PathNode(false, CreateNameEntry(nodeName)));
nextSubnode = r;
}
currentNode = nextSubnode;
}
}
return true;
}
uint32_t ZArchiveWriter::CreateNameEntry(std::string_view name)
{
auto it = m_nodeNameLookup.find(std::string(name));
if (it != m_nodeNameLookup.end())
return it->second;
uint32_t nameIndex = (uint32_t)m_nodeNames.size();
m_nodeNames.emplace_back(name);
m_nodeNameLookup.emplace(name, nameIndex);
return nameIndex;
}
void ZArchiveWriter::OutputData(const void* data, size_t length)
{
m_cbWriteOutputData(data, length, m_cbCtx);
m_currentCompressedWriteIndex += length;
// hash the data
if (m_mainShaCtx)
sha_256_write(m_mainShaCtx, data, length);
}
uint64_t ZArchiveWriter::GetCurrentOutputOffset() const
{
return m_currentCompressedWriteIndex;
}
void ZArchiveWriter::StoreBlock(const uint8_t* uncompressedData)
{
// compress and store
uint64_t compressedWriteOffset = GetCurrentOutputOffset();
m_compressionBuffer.resize(ZSTD_compressBound(_ZARCHIVE::COMPRESSED_BLOCK_SIZE));
size_t outputSize = ZSTD_compress(m_compressionBuffer.data(), m_compressionBuffer.size(), uncompressedData, _ZARCHIVE::COMPRESSED_BLOCK_SIZE, 6);
assert(outputSize >= 0);
if (outputSize >= _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
{
// store block uncompressed if it is equal or larger than the input after compression
outputSize = _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
OutputData(uncompressedData, _ZARCHIVE::COMPRESSED_BLOCK_SIZE);
}
else
{
OutputData(m_compressionBuffer.data(), outputSize);
}
// add offset translation record
if ((m_numWrittenOffsetRecords % _ZARCHIVE::ENTRIES_PER_OFFSETRECORD) == 0)
m_compressionOffsetRecord.emplace_back().baseOffset = compressedWriteOffset;
m_compressionOffsetRecord.back().size[m_numWrittenOffsetRecords % _ZARCHIVE::ENTRIES_PER_OFFSETRECORD] = (uint16_t)outputSize - 1;
m_numWrittenOffsetRecords++;
}
void ZArchiveWriter::AppendData(const void* data, size_t size)
{
size_t dataSize = size;
const uint8_t* input = (const uint8_t*)data;
while (size > 0)
{
size_t bytesToCopy = _ZARCHIVE::COMPRESSED_BLOCK_SIZE - m_currentWriteBuffer.size();
if (bytesToCopy > size)
bytesToCopy = size;
if (bytesToCopy == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
{
// if incoming data is block-aligned we can store it directly without memcpy to temporary buffer
StoreBlock(input);
input += bytesToCopy;
size -= bytesToCopy;
continue;
}
m_currentWriteBuffer.insert(m_currentWriteBuffer.end(), input, input + bytesToCopy);
input += bytesToCopy;
size -= bytesToCopy;
if (m_currentWriteBuffer.size() == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
{
StoreBlock(m_currentWriteBuffer.data());
m_currentWriteBuffer.clear();
}
}
if (m_currentFileNode)
m_currentFileNode->fileSize += dataSize;
m_currentInputOffset += dataSize;
}
void ZArchiveWriter::Finalize()
{
m_currentFileNode = nullptr; // make sure the padding added below doesn't modify the active file
// flush write buffer by padding it to the length of a full block
if (!m_currentWriteBuffer.empty())
{
std::vector<uint8_t> padBuffer;
padBuffer.resize(_ZARCHIVE::COMPRESSED_BLOCK_SIZE - m_currentWriteBuffer.size());
AppendData(padBuffer.data(), padBuffer.size());
}
m_footer.sectionCompressedData.offset = 0;
m_footer.sectionCompressedData.size = GetCurrentOutputOffset();
// pad to 8 byte
while ((GetCurrentOutputOffset() % 8) != 0)
{
uint8_t b = 0;
OutputData(&b, sizeof(uint8_t));
}
WriteOffsetRecords();
WriteNameTable();
WriteFileTree();
WriteMetaData();
WriteFooter();
}
void ZArchiveWriter::WriteOffsetRecords()
{
m_footer.sectionOffsetRecords.offset = GetCurrentOutputOffset();
_ZARCHIVE::CompressionOffsetRecord::Serialize(m_compressionOffsetRecord.data(), m_compressionOffsetRecord.size(), m_compressionOffsetRecord.data()); // in-place
OutputData(m_compressionOffsetRecord.data(), m_compressionOffsetRecord.size() * sizeof(_ZARCHIVE::CompressionOffsetRecord));
m_footer.sectionOffsetRecords.size = GetCurrentOutputOffset() - m_footer.sectionOffsetRecords.offset;
}
void ZArchiveWriter::WriteNameTable()
{
m_footer.sectionNames.offset = GetCurrentOutputOffset();
uint32_t currentNameTableOffset = 0;
m_nodeNameOffsets.resize(m_nodeNames.size());
for (size_t i = 0; i < m_nodeNames.size(); i++)
{
m_nodeNameOffsets[i] = currentNameTableOffset;
// Each node name is stored with a length prefix byte. The prefix byte's MSB is used to indicate if an extended 2-byte header is used. The lower 7 bits are used to store the lower bits of the name length
// If MSB is set, add an extra byte which extends the 7 bit name length field to 15 bit
std::string_view name = m_nodeNames[i];
if (name.size() > 0x7FFF)
name = name.substr(0, 0x7FFF); // cut-off after 2^15-1 characters
if (name.size() >= 0x80)
{
uint8_t header[2];
header[0] = (uint8_t)(name.size() & 0x7F) | 0x80;
header[1] = (uint8_t)(name.size() >> 7);
OutputData(header, 2);
currentNameTableOffset += 2;
}
else
{
uint8_t header[1];
header[0] = (uint8_t)name.size() & 0x7F;
OutputData(header, 1);
currentNameTableOffset += 1;
}
OutputData(name.data(), name.size());
currentNameTableOffset += (uint32_t)name.size();
}
m_footer.sectionNames.size = GetCurrentOutputOffset() - m_footer.sectionNames.offset;
}
void ZArchiveWriter::WriteFileTree()
{
std::queue<PathNode*> nodeQueue;
// first pass - assign a node range to all directories
nodeQueue.push(&m_rootNode);
uint32_t currentIndex = 1; // root node is at index 0
while (!nodeQueue.empty())
{
PathNode* node = nodeQueue.front();
nodeQueue.pop();
if (node->isFile)
{
node->nodeStartIndex = (uint32_t)0xFFFFFFFF;
continue;
}
// order entries lexicographically so we can use binary search in the reader
std::sort(node->subnodes.begin(), node->subnodes.end(),
[&](ZArchiveWriter::PathNode*& a, ZArchiveWriter::PathNode*& b) -> int
{
return _ZARCHIVE::CompareNodeName(m_nodeNames[a->nameIndex], m_nodeNames[b->nameIndex]) > 0;
});
node->nodeStartIndex = currentIndex;
currentIndex += (uint32_t)node->subnodes.size();
for (auto& it : node->subnodes)
nodeQueue.push(it);
}
// second pass - serialize to file
m_footer.sectionFileTree.offset = GetCurrentOutputOffset();
nodeQueue.push(&m_rootNode);
while (!nodeQueue.empty())
{
PathNode* node = nodeQueue.front();
nodeQueue.pop();
_ZARCHIVE::FileDirectoryEntry tmp;
if(node == &m_rootNode)
tmp.SetTypeAndNameOffset(node->isFile, 0x7FFFFFFF);
else
tmp.SetTypeAndNameOffset(node->isFile, m_nodeNameOffsets[node->nameIndex]);
if (node->isFile)
{
tmp.SetFileOffset(node->fileOffset);
tmp.SetFileSize(node->fileSize);
}
else
{
tmp.directoryRecord.count = (uint32_t)node->subnodes.size();
tmp.directoryRecord.nodeStartIndex = node->nodeStartIndex;
tmp.directoryRecord._reserved = 0;
}
_ZARCHIVE::FileDirectoryEntry::Serialize(&tmp, 1, &tmp);
OutputData(&tmp, sizeof(_ZARCHIVE::FileDirectoryEntry));
for (auto& it : node->subnodes)
nodeQueue.push(it);
}
m_footer.sectionFileTree.size = GetCurrentOutputOffset() - m_footer.sectionFileTree.offset;
}
void ZArchiveWriter::WriteMetaData()
{
// todo
m_footer.sectionMetaDirectory.offset = GetCurrentOutputOffset();
m_footer.sectionMetaDirectory.size = 0;
m_footer.sectionMetaData.offset = GetCurrentOutputOffset();
m_footer.sectionMetaData.size = 0;
}
void ZArchiveWriter::WriteFooter()
{
m_footer.magic = _ZARCHIVE::Footer::kMagic;
m_footer.version = _ZARCHIVE::Footer::kVersion1;
m_footer.totalSize = GetCurrentOutputOffset() + sizeof(_ZARCHIVE::Footer);
_ZARCHIVE::Footer tmp;
// serialize and hash the footer with all hash bytes set to zero
memset(m_footer.integrityHash, 0, 32);
_ZARCHIVE::Footer::Serialize(&m_footer, &tmp);
sha_256_write(m_mainShaCtx, &tmp, sizeof(_ZARCHIVE::Footer));
sha_256_close(m_mainShaCtx);
free(m_mainShaCtx);
m_mainShaCtx = nullptr;
// set hash and write footer
memcpy(m_footer.integrityHash, m_integritySha, 32);
_ZARCHIVE::Footer::Serialize(&m_footer, &tmp);
OutputData(&tmp, sizeof(_ZARCHIVE::Footer));
}
07070100000015000081A4000000000000000000000001669D41C60000002E000000000000000000000000000000000000002E00000000ZArchive-0.1.2+git20240721.b467f7a/vcpkg.json{
"dependencies": [
"zstd"
]
}07070100000016000081A4000000000000000000000001669D41C600000119000000000000000000000000000000000000003200000000ZArchive-0.1.2+git20240721.b467f7a/zarchive.pc.inprefix=@CMAKE_INSTALL_PREFIX@
includedir=@PKGCONFIG_INCLUDEDIR@
libdir=@PKGCONFIG_LIBDIR@
Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
URL: @PROJECT_HOMEPAGE_URL@
Version: @PROJECT_VERSION@
Requires.private: libzstd
Libs: -L${libdir} -lzarchive
Cflags: -I${includedir}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!152 blocks