aboutsummaryrefslogtreecommitdiff
path: root/libs/bsapacker/src/TextureArchiveBuilder.cpp
blob: 123a48d3bd5d710c22b0c7e3a32cbe93c8529d72 (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <bsapacker/TextureArchiveBuilder.h>

#include <bsapacker/ArchiveBuilderHelper.h>
#include <QDirIterator>
#include <QApplication>
#include <QDebug>

#include <cstdint>
#include <filesystem>
#include <fstream>

using namespace libbsarch;

namespace BsaPacker
{
	// 4 GiB limit for the Fallout 4 Creation Kit. The game itself has no limit so could make an optional setting
	// This does not consider size after compression or share data
	const qint64 TextureArchiveBuilder::SIZE_LIMIT = (qint64)1024 * 1024 * 1024 * 4;

	TextureArchiveBuilder::TextureArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type)
		: m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir), m_ArchiveType(type)
	{
		this->m_Cancelled = false;
		this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
	}

	uint32_t TextureArchiveBuilder::setFiles()
	{
		uint32_t compressibleFiles = 0;
		int count = 0;
		qint64 currentSize = 0;
		const auto& dirString = (this->m_RootDirectory.path() + '/').toStdWString();
		const auto& rootDirFiles = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(dirString);
		qDebug() << "root is: " << m_RootDirectory.path() + '/';

		QDirIterator iterator(this->m_RootDirectory.absolutePath(), QDir::Files, QDirIterator::Subdirectories);
		while (iterator.hasNext()) {
			QApplication::processEvents();

			if (this->m_Cancelled) {
				for (auto& archive : this->m_Archives) {
					archive.reset();
				}
				return 0;
			}

			const QFileInfo& fileInfo = iterator.nextFileInfo();
			const QString& filepath = fileInfo.absoluteFilePath();
			const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath.toStdWString(), rootDirFiles);

			Q_EMIT this->valueChanged(++count);
			if (ignored || !filepath.endsWith(".dds", Qt::CaseInsensitive)) {
				continue;
			}

			currentSize += fileInfo.size();
			if (currentSize > SIZE_LIMIT) {
				currentSize = fileInfo.size();
				this->m_Archives.back()->set_compressed(true);
				this->m_Archives.back()->set_dds_callback(TextureArchiveBuilder::DDSCallback, this->getRootPath().toStdWString());
				compressibleFiles = 0;
				this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
				this->setShareData(true);
			}

			++compressibleFiles;
			auto fileBlob = disk_blob(
				 dirString,
				 filepath.toStdWString());
			this->m_Archives.back()->add_file_from_disk(fileBlob);
			qDebug() << "file is: " << filepath;
		}
		this->m_Archives.back()->set_compressed(true);
		this->m_Archives.back()->set_dds_callback(TextureArchiveBuilder::DDSCallback, this->getRootPath().toStdWString());
		return compressibleFiles;
	}

	void TextureArchiveBuilder::setShareData(const bool value)
	{
		this->m_Archives.back()->set_share_data(value);
	}

	std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> TextureArchiveBuilder::getArchives()
	{
		return std::move(this->m_Archives);
	}

	uint32_t TextureArchiveBuilder::getFileCount() const
	{
		return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory.path().toStdWString());
	}

	QString TextureArchiveBuilder::getRootPath() const
	{
		return this->m_RootDirectory.path();
	}

	void TextureArchiveBuilder::cancel()
	{
		this->m_Cancelled = true;
	}

	void TextureArchiveBuilder::DDSCallback(bsa_archive_t, const wchar_t* file_path, bsa_dds_info_t* dds_info, void* context)
	{
		const auto& path = *static_cast<std::wstring*>(context) + L'/' + std::wstring(file_path);
		std::ifstream file(std::filesystem::path(path), std::ios::binary);
		if (!file) {
			throw std::runtime_error("Failed to open DDS");
		}

		uint8_t header[128] = {};
		file.read(reinterpret_cast<char*>(header), sizeof(header));
		if (file.gcount() != static_cast<std::streamsize>(sizeof(header))) {
			throw std::runtime_error("Invalid DDS header");
		}

		if (!(header[0] == 'D' && header[1] == 'D' && header[2] == 'S' && header[3] == ' ')) {
			throw std::runtime_error("Not a DDS file");
		}

		auto le32 = [](const uint8_t* p) -> uint32_t {
			return static_cast<uint32_t>(p[0]) |
				   (static_cast<uint32_t>(p[1]) << 8) |
				   (static_cast<uint32_t>(p[2]) << 16) |
				   (static_cast<uint32_t>(p[3]) << 24);
		};

		// DDS_HEADER starts at byte 4.
		const uint32_t height = le32(&header[12]);  // 4 + 8
		const uint32_t width = le32(&header[16]);   // 4 + 12
		const uint32_t mips = le32(&header[28]);    // 4 + 24

		dds_info->width = width;
		dds_info->height = height;
		dds_info->mipmaps = mips > 0 ? mips : 1;
	}
} // namespace BsaPacker