summaryrefslogtreecommitdiffhomepage
path: root/ArchiveEx/Archive.cpp
blob: 3eea0fe5fc8f3aefee91ad2ca11cc41cef78d520 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include "Archive.h"

#include <cstdint>
#include <cstdio>
#include <memory>
#include <vector>

#include <zlib.h>


namespace Archive
{


using UniqueFileHandle = std::unique_ptr<std::FILE, decltype(&std::fclose)>;


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 = std::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<Bytef> 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<Bytef*>(entries.data()),
		&uncompressed_size,
		compressed.data(),
		header.directory.compressed_size);
	if (Z_OK != err)
		throw "could not uncompress directory";
}


int
Archive::Expand(const int index, std::uint8_t*& buffer, const bool null_terminated) const
{
	if (0 > index && static_cast<int>(header.total_entries) <= index)
		return -1;  // Can be replaced by error-checked access to std::vector holding entries.
	UniqueFileHandle file {std::fopen(path, "rb"), &std::fclose};
	if (!file)
		return -1;
	const auto& entry = entries[index];
	std::vector<Bytef> compressed(entry.compressed_size);
	int err = std::fseek(file.get(), sizeof(Header) + entry.offset, SEEK_SET);
	if (-1 == err)
		return -1;
	std::size_t length = std::fread(compressed.data(), 1, entry.compressed_size, file.get());
	if (entry.compressed_size != length)
		return -1;
	uLongf output_length = entry.original_size + static_cast<uLongf>(null_terminated);
	std::unique_ptr<std::uint8_t[]> uncompressed(new std::uint8_t[output_length]);
	if (!uncompressed)
		return -1;
	err = uncompress(uncompressed.get(), &output_length, compressed.data(), entry.compressed_size);
	if (Z_OK != err)
		return -1;
	buffer = uncompressed.release();
	if (null_terminated)
		buffer[output_length - 1] = 0;
	return output_length;
}


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