aboutsummaryrefslogtreecommitdiff
path: root/libs/bsatk/src/bsaarchive.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/bsatk/src/bsaarchive.cpp
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/bsatk/src/bsaarchive.cpp')
-rw-r--r--libs/bsatk/src/bsaarchive.cpp1237
1 files changed, 1237 insertions, 0 deletions
diff --git a/libs/bsatk/src/bsaarchive.cpp b/libs/bsatk/src/bsaarchive.cpp
new file mode 100644
index 0000000..e1655c1
--- /dev/null
+++ b/libs/bsatk/src/bsaarchive.cpp
@@ -0,0 +1,1237 @@
+/*
+Mod Organizer BSA handling
+
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "bsaarchive.h"
+#include "bsaexception.h"
+#include "bsafile.h"
+#include "bsafolder.h"
+#include <algorithm>
+#include <boost/interprocess/sync/interprocess_semaphore.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/thread.hpp>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <lz4.h>
+#include <lz4frame.h>
+#include <memory>
+#include <queue>
+#include <sys/stat.h>
+#include <zlib.h>
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#endif
+
+using std::fstream;
+
+using namespace boost::posix_time;
+
+namespace BSA
+{
+
+Archive::Archive()
+ : m_RootFolder(new Folder), m_ArchiveFlags(FLAG_HASDIRNAMES | FLAG_HASFILENAMES),
+ m_Type(TYPE_SKYRIM)
+{}
+
+Archive::~Archive()
+{
+ if (m_File.is_open()) {
+ m_File.close();
+ }
+ std::vector<Folder::Ptr> folders;
+ m_RootFolder->collectFolders(folders);
+ cleanFolder(m_RootFolder);
+ m_RootFolder.reset();
+}
+
+ArchiveType Archive::typeFromID(BSAULong typeID)
+{
+ switch (typeID) {
+ case 0x100:
+ return TYPE_MORROWIND;
+ case 0x67:
+ return TYPE_OBLIVION;
+ case 0x68:
+ return TYPE_FALLOUT3;
+ case 0x69:
+ return TYPE_SKYRIMSE;
+ case 0x01:
+ return TYPE_FALLOUT4;
+ case 0x02:
+ return TYPE_STARFIELD;
+ case 0x03:
+ return TYPE_STARFIELD_LZ4_TEXTURE;
+ case 0x07:
+ return TYPE_FALLOUT4NG_7;
+ case 0x08:
+ return TYPE_FALLOUT4NG_8;
+ default:
+ throw data_invalid_exception(makeString("invalid type %d", typeID));
+ }
+}
+
+BSAULong Archive::typeToID(ArchiveType type)
+{
+ switch (type) {
+ case TYPE_MORROWIND:
+ return 0x100;
+ case TYPE_OBLIVION:
+ return 0x67;
+ case TYPE_FALLOUT3:
+ return 0x68;
+ case TYPE_SKYRIMSE:
+ return 0x69;
+ case TYPE_FALLOUT4:
+ return 0x01;
+ case TYPE_STARFIELD:
+ return 0x02;
+ case TYPE_STARFIELD_LZ4_TEXTURE:
+ return 0x03;
+ case TYPE_FALLOUT4NG_7:
+ return 0x07;
+ case TYPE_FALLOUT4NG_8:
+ return 0x08;
+ default:
+ throw data_invalid_exception(makeString("invalid type %d", type));
+ }
+}
+
+Archive::Header Archive::readHeader(std::fstream& infile)
+{
+ Header result;
+
+ result.fileIdentifier = readType<uint32_t>(infile);
+ if (result.fileIdentifier != 0x00415342 && result.fileIdentifier != 0x58445442 &&
+ result.fileIdentifier != 0x00000100) {
+ throw data_invalid_exception(makeString("not a bsa or ba2 file"));
+ }
+
+ if (result.fileIdentifier != 0x00000100) {
+ ArchiveType type = typeFromID(readType<BSAUInt>(infile));
+ if (type == TYPE_FALLOUT4 || type == TYPE_STARFIELD ||
+ type == TYPE_STARFIELD_LZ4_TEXTURE || type == TYPE_FALLOUT4NG_7 ||
+ type == TYPE_FALLOUT4NG_8) {
+ result.type = type;
+ infile.read(result.archType, 4);
+ result.archType[4] = '\0';
+ result.fileCount = readType<BSAUInt>(infile);
+ result.nameTableOffset = readType<BSAHash>(infile);
+ result.archiveFlags = FLAG_HASDIRNAMES | FLAG_HASFILENAMES;
+ } else {
+ result.type = type;
+ result.offset = readType<BSAUInt>(infile);
+ result.archiveFlags = readType<BSAUInt>(infile);
+ result.folderCount = readType<BSAUInt>(infile);
+ result.fileCount = readType<BSAUInt>(infile);
+ result.folderNameLength = readType<BSAUInt>(infile);
+ result.fileNameLength = readType<BSAUInt>(infile);
+ result.fileFlags = readType<BSAUInt>(infile);
+ }
+ } else {
+ result.type = TYPE_MORROWIND;
+ result.offset = readType<BSAUInt>(infile);
+ result.fileCount = readType<BSAUInt>(infile);
+ result.archiveFlags = FLAG_HASDIRNAMES | FLAG_HASFILENAMES;
+ }
+
+ return result;
+}
+
+EErrorCode Archive::read(const char* fileName, bool testHashes)
+{
+ m_File.open(fileName, fstream::in | fstream::binary);
+ if (!m_File.is_open()) {
+ return ERROR_FILENOTFOUND;
+ }
+ m_File.exceptions(std::ios_base::badbit);
+ try {
+ Header header;
+ try {
+ header = readHeader(m_File);
+ } catch (const data_invalid_exception& e) {
+ throw data_invalid_exception(makeString("%s (filename: %s)", e.what(), fileName));
+ }
+ m_ArchiveFlags = header.archiveFlags;
+ m_Type = header.type;
+ if (m_Type == TYPE_FALLOUT4 || m_Type == TYPE_STARFIELD ||
+ m_Type == TYPE_STARFIELD_LZ4_TEXTURE || m_Type == TYPE_FALLOUT4NG_7 ||
+ m_Type == TYPE_FALLOUT4NG_8) {
+
+ m_File.seekg(header.nameTableOffset);
+
+ std::vector<std::string> fileNames;
+ for (unsigned int i = 0; i < header.fileCount; ++i) {
+ BSAUShort length = readType<BSAUShort>(m_File);
+
+ char* file = new char[length + 1];
+ m_File.read(file, length);
+ file[length] = '\0';
+
+ fileNames.push_back(file);
+ delete[] file;
+ }
+ std::streamoff offset;
+ switch (m_Type) {
+ case TYPE_STARFIELD:
+ offset = 32;
+ break;
+ case TYPE_STARFIELD_LZ4_TEXTURE:
+ offset = 36;
+ break;
+ default:
+ offset = 24;
+ }
+ if (strcmp(header.archType, "GNRL") == 0) {
+ m_File.seekg(offset, std::ios::beg);
+ for (unsigned int i = 0; i < header.fileCount; ++i) {
+ BSAUInt nameHash = readType<BSAUInt>(m_File);
+ char* extension = new char[4];
+ m_File.read(extension, 4);
+ BSAUInt dirHash = readType<BSAUInt>(m_File);
+ m_File.seekg(4, std::ios::cur);
+ BSAHash offset = readType<BSAHash>(m_File);
+ BSAUInt packedSize = readType<BSAUInt>(m_File);
+ BSAUInt unpackedSize = readType<BSAUInt>(m_File);
+ m_File.seekg(4, std::ios::cur);
+ std::vector<FO4TextureChunk> dummy;
+ Folder::Ptr newDir = m_RootFolder->addFolderFromFile(
+ fileNames[i], packedSize, offset, unpackedSize, {}, dummy);
+ delete[] extension;
+ }
+ } else if (strcmp(header.archType, "DX10") == 0) {
+ m_File.seekg(offset, std::ios::beg);
+ for (unsigned int i = 0; i < header.fileCount; ++i) {
+ FO4TextureHeader texHeader;
+ texHeader.nameHash = readType<BSAUInt>(m_File);
+ m_File.read(texHeader.extension, 4);
+ texHeader.dirHash = readType<BSAUInt>(m_File);
+ texHeader.unknown1 = readType<BSAUChar>(m_File);
+ texHeader.chunkNumber = readType<BSAUChar>(m_File);
+ texHeader.chunkHeaderSize = readType<BSAUShort>(m_File);
+ texHeader.height = readType<BSAUShort>(m_File);
+ texHeader.width = readType<BSAUShort>(m_File);
+ texHeader.mipCount = readType<BSAUChar>(m_File);
+ texHeader.format = static_cast<DXGI_FORMAT>(readType<BSAUChar>(m_File));
+ texHeader.isCubemap = readType<bool>(m_File);
+ texHeader.unknown2 = readType<BSAUChar>(m_File);
+ std::vector<FO4TextureChunk> chunks;
+ for (unsigned int j = 0; j < texHeader.chunkNumber; ++j) {
+ FO4TextureChunk chunk;
+ chunk.offset = readType<BSAHash>(m_File);
+ chunk.packedSize = readType<BSAUInt>(m_File);
+ chunk.unpackedSize = readType<BSAUInt>(m_File);
+ chunk.startMip = readType<BSAUShort>(m_File);
+ chunk.endMip = readType<BSAUShort>(m_File);
+ chunk.unknown = readType<BSAUInt>(m_File);
+ chunks.push_back(chunk);
+ }
+ Folder::Ptr newDir = m_RootFolder->addFolderFromFile(
+ fileNames[i], chunks[0].packedSize, chunks[0].offset,
+ chunks[0].unpackedSize, texHeader, chunks);
+ }
+ }
+
+ return ERROR_NONE;
+ } else if (m_Type == TYPE_MORROWIND) {
+ BSAUInt dataOffset = 12 + header.offset + header.fileCount * 8;
+
+ std::vector<MorrowindFileOffset> fileSizeOffset(header.fileCount);
+ m_File.read((char*)fileSizeOffset.data(),
+ header.fileCount * sizeof(MorrowindFileOffset));
+ std::vector<BSAUInt> fileNameOffset(header.fileCount);
+ m_File.read((char*)fileNameOffset.data(), header.fileCount * sizeof(BSAUInt));
+ BSAUInt last = header.offset - 12 * header.fileCount;
+ for (uint32_t i = 0; i < header.fileCount; ++i) {
+ uint32_t index = 0;
+ if (i + 1 == header.fileCount)
+ index = last;
+ else
+ index = fileNameOffset[i + 1] - fileNameOffset[i];
+ char* filePath = new char[index + 1];
+ m_File.read(filePath, index);
+ filePath[index] = '\0';
+
+ std::vector<FO4TextureChunk> dummy;
+ Folder::Ptr newDir = m_RootFolder->addFolderFromFile(
+ filePath, fileSizeOffset[i].size, dataOffset + fileSizeOffset[i].offset, 0,
+ {}, dummy);
+ delete[] filePath;
+ }
+ return ERROR_NONE;
+ } else {
+ // flat list of folders as they were stored in the archive
+ std::vector<Folder::Ptr> folders;
+
+ for (unsigned long i = 0; i < header.folderCount; ++i) {
+ folders.push_back(m_RootFolder->addFolder(m_File, header.fileNameLength,
+ header.offset, header.type));
+ }
+
+ m_File.seekg(header.offset);
+
+ bool hashesValid = true;
+ for (std::vector<Folder::Ptr>::iterator iter = folders.begin();
+ iter != folders.end(); ++iter) {
+ if (!(*iter)->resolveFileNames(m_File, testHashes)) {
+ hashesValid = false;
+ }
+ }
+ return hashesValid ? ERROR_NONE : ERROR_INVALIDHASHES;
+ }
+ } catch (std::ios_base::failure&) {
+ return ERROR_INVALIDDATA;
+ }
+}
+
+void Archive::close()
+{
+ m_File.close();
+}
+
+BSAULong Archive::countFiles() const
+{
+ return m_RootFolder->countFiles();
+}
+
+std::vector<std::string> Archive::collectFolderNames() const
+{
+ std::vector<std::string> result;
+ m_RootFolder->collectFolderNames(result);
+ return result;
+}
+
+std::vector<std::string> Archive::collectFileNames() const
+{
+ std::vector<std::string> result;
+ m_RootFolder->collectFileNames(result);
+ return result;
+}
+
+BSAULong Archive::countCharacters(const std::vector<std::string>& list) const
+{
+ size_t sum = 0;
+ for (std::vector<std::string>::const_iterator iter = list.begin(); iter != list.end();
+ ++iter) {
+ sum += iter->length() + 1;
+ }
+ return static_cast<BSAULong>(sum);
+}
+
+#ifndef WIN32
+#define _stricmp strcasecmp
+#endif // WIN32
+
+static bool endsWith(const std::string& fileName, const char* extension)
+{
+ size_t endLength = strlen(extension);
+ if (fileName.length() < endLength) {
+ return false;
+ }
+ return _stricmp(&fileName[fileName.length() - endLength], extension) == 0;
+}
+
+BSAULong Archive::determineFileFlags(const std::vector<std::string>& fileList) const
+{
+ BSAULong result = 0;
+
+ bool hasNIF = false;
+ bool hasDDS = false;
+ bool hasXML = false;
+ bool hasWAV = false;
+ bool hasMP3 = false;
+ bool hasTXT = false;
+ bool hasSPT = false;
+ bool hasTEX = false;
+ bool hasCTL = false;
+
+ for (std::vector<std::string>::const_iterator iter = fileList.begin();
+ iter != fileList.end(); ++iter) {
+ if (!hasNIF && endsWith(*iter, ".nif")) {
+ hasNIF = true;
+ result |= 1 << 0;
+ } else if (!hasDDS && endsWith(*iter, ".dds")) {
+ hasDDS = true;
+ result |= 1 << 1;
+ } else if (!hasXML && endsWith(*iter, ".xml")) {
+ hasXML = true;
+ result |= 1 << 2;
+ } else if (!hasWAV && endsWith(*iter, ".wav")) {
+ hasWAV = true;
+ result |= 1 << 3;
+ } else if (!hasMP3 && endsWith(*iter, ".mp3")) {
+ hasMP3 = true;
+ result |= 1 << 4;
+ } else if (!hasTXT && endsWith(*iter, ".txt")) {
+ hasTXT = true;
+ result |= 1 << 5;
+ } else if (!hasSPT && endsWith(*iter, ".spt")) {
+ hasSPT = true;
+ result |= 1 << 6;
+ } else if (!hasTEX && endsWith(*iter, ".tex")) {
+ hasTEX = true;
+ result |= 1 << 7;
+ } else if (!hasCTL && endsWith(*iter, ".ctl")) {
+ hasCTL = true;
+ result |= 1 << 8;
+ }
+ }
+ return result;
+}
+
+void Archive::writeHeader(std::fstream& outfile, BSAULong fileFlags,
+ BSAULong numFolders, BSAULong folderNamesLength,
+ BSAULong fileNamesLength)
+{
+ outfile.write("BSA\0", 4);
+ writeType<BSAULong>(outfile, typeToID(m_Type));
+ writeType<BSAULong>(outfile, 0x24); // header size is static
+ writeType<BSAULong>(outfile, m_ArchiveFlags);
+ writeType<BSAULong>(outfile, numFolders);
+ writeType<BSAULong>(outfile, countFiles());
+ writeType<BSAULong>(outfile, folderNamesLength);
+ writeType<BSAULong>(outfile, fileNamesLength);
+ writeType<BSAULong>(outfile, fileFlags);
+}
+
+EErrorCode Archive::write(const char* fileName)
+{
+ std::fstream outfile;
+ outfile.open(fileName, fstream::out | fstream::binary);
+ if (!outfile.is_open()) {
+ return ERROR_ACCESSFAILED;
+ }
+ outfile.exceptions(std::ios_base::badbit);
+
+ std::vector<Folder::Ptr> folders;
+ m_RootFolder->collectFolders(folders);
+
+ std::vector<std::string> folderNames;
+ std::vector<std::string> fileNames;
+ BSAULong folderNamesLength = 0;
+ BSAULong fileNamesLength = 0;
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ std::string fullPath = (*folderIter)->getFullPath();
+ folderNames.push_back(fullPath);
+ folderNamesLength += static_cast<BSAULong>(fullPath.length());
+ for (std::vector<File::Ptr>::const_iterator fileIter =
+ (*folderIter)->m_Files.begin();
+ fileIter != (*folderIter)->m_Files.end(); ++fileIter) {
+ fileNames.push_back((*fileIter)->m_Name);
+ fileNamesLength += static_cast<BSAULong>((*fileIter)->m_Name.length());
+ }
+ }
+
+ try {
+ writeHeader(outfile, determineFileFlags(fileNames),
+ static_cast<BSAULong>(folderNames.size()), folderNamesLength,
+ fileNamesLength);
+#pragma message("folders (and files?) need to be sorted by hash!")
+ // dummy-pass: before we can store the actual folder data
+
+ // prepare folder and file headers
+#pragma message("it's unnecessary to write actual data, placeholders are sufficient")
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ (*folderIter)->writeHeader(outfile);
+ }
+
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ (*folderIter)->writeData(outfile, fileNamesLength);
+ }
+
+ // write file names
+ for (std::vector<std::string>::const_iterator folderIter = fileNames.begin();
+ folderIter != fileNames.end(); ++folderIter) {
+ writeZString(outfile, *folderIter);
+ }
+
+ // write file data
+ for (std::vector<Folder::Ptr>::iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ (*folderIter)->writeFileData(m_File, outfile);
+ }
+
+ outfile.seekp(0x24, fstream::beg);
+
+ // re-write folder and file structure, this time with the correct
+ // offsets
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ (*folderIter)->writeHeader(outfile);
+ }
+
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = folders.begin();
+ folderIter != folders.end(); ++folderIter) {
+ (*folderIter)->writeData(outfile, fileNamesLength);
+ }
+
+ outfile.close();
+ return ERROR_NONE;
+ } catch (std::ios_base::failure&) {
+ outfile.close();
+ return ERROR_INVALIDDATA;
+ }
+}
+
+DirectX::DDS_HEADER Archive::getDDSHeader(File::Ptr file,
+ DirectX::DDS_HEADER_DXT10& DX10Header,
+ bool& isDX10) const
+{
+ DirectX::DDS_HEADER DDSHeaderData = {};
+ DDSHeaderData.size = sizeof(DDSHeaderData);
+ DDSHeaderData.flags =
+ DDS_HEADER_FLAGS_TEXTURE | DDS_HEADER_FLAGS_LINEARSIZE | DDS_HEADER_FLAGS_MIPMAP;
+ DDSHeaderData.height = file->m_TextureHeader.height;
+ DDSHeaderData.width = file->m_TextureHeader.width;
+ DDSHeaderData.mipMapCount = file->m_TextureHeader.mipCount;
+ DDSHeaderData.ddspf.size = sizeof(DirectX::DDS_PIXELFORMAT);
+ DDSHeaderData.caps = DDS_SURFACE_FLAGS_TEXTURE | DDS_SURFACE_FLAGS_MIPMAP;
+
+ if (file->m_TextureHeader.isCubemap)
+ DDSHeaderData.caps2 = DDS_CUBEMAP_ALLFACES;
+
+ bool supported = true;
+
+ switch (file->m_TextureHeader.format) {
+ case DXGI_FORMAT_BC1_UNORM:
+ case DXGI_FORMAT_BC1_UNORM_SRGB:
+ DDSHeaderData.ddspf = DirectX::DDSPF_DXT1;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height / 2;
+ break;
+
+ case DXGI_FORMAT_BC2_UNORM:
+ case DXGI_FORMAT_BC2_UNORM_SRGB:
+ DDSHeaderData.ddspf = DirectX::DDSPF_DXT3;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC3_UNORM:
+ case DXGI_FORMAT_BC3_UNORM_SRGB:
+ DDSHeaderData.ddspf = DirectX::DDSPF_DXT5;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC4_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_BC4_UNORM;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC4_SNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_BC4_SNORM;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC5_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_BC5_UNORM;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC5_SNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_BC5_SNORM;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+ break;
+
+ case DXGI_FORMAT_BC7_UNORM:
+ case DXGI_FORMAT_BC7_UNORM_SRGB:
+ DDSHeaderData.ddspf = DirectX::DDSPF_DX10;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height;
+
+ isDX10 = true;
+ DX10Header.dxgiFormat = file->m_TextureHeader.format;
+ break;
+
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_A8B8G8R8;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height * 4; // 32bpp
+ break;
+
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_A8R8G8B8;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height * 4; // 32bpp
+ break;
+
+ case DXGI_FORMAT_B8G8R8X8_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_X8R8G8B8;
+ break;
+
+ case DXGI_FORMAT_R8_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_L8;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height; // 8bpp
+ break;
+
+ case DXGI_FORMAT_R16_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_L16;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height * 2; // 16bpp
+ break;
+
+ case DXGI_FORMAT_R8G8_UNORM:
+ DDSHeaderData.ddspf = DirectX::DDSPF_A8L8;
+ DDSHeaderData.pitchOrLinearSize =
+ file->m_TextureHeader.width * file->m_TextureHeader.height * 2; // 16bpp
+ break;
+
+ default:
+ return {};
+ break;
+ }
+
+ return DDSHeaderData;
+}
+
+void Archive::getDX10Header(DirectX::DDS_HEADER_DXT10& DX10Header, File::Ptr file,
+ DirectX::DDS_HEADER DDSHeader) const
+{
+ DX10Header.resourceDimension = DirectX::DDS_DIMENSION_TEXTURE2D;
+ DX10Header.miscFlag = 0;
+ DX10Header.arraySize = 1;
+ DX10Header.miscFlags2 = 0;
+}
+
+static const unsigned long CHUNK_SIZE = 128 * 1024;
+
+EErrorCode Archive::extractDirect(File::Ptr file, std::ofstream& outFile) const
+{
+ EErrorCode result = ERROR_NONE;
+ if (file->m_FileSize == 0) {
+ // don't try to read empty file
+ return result;
+ }
+
+ m_File.clear();
+ m_File.seekg(static_cast<std::ifstream::pos_type>(file->m_DataOffset), std::ios::beg);
+
+ if (m_Type == TYPE_FALLOUT4 || m_Type == TYPE_STARFIELD ||
+ m_Type == TYPE_STARFIELD_LZ4_TEXTURE || m_Type == TYPE_FALLOUT4NG_7 ||
+ m_Type == TYPE_FALLOUT4NG_8) {
+ if (!file->m_TextureChunks.size()) {
+ BSAULong size = file->m_UncompressedFileSize;
+ std::unique_ptr<char[]> buffer(new char[size]);
+ m_File.read(buffer.get(), size);
+ outFile.write(buffer.get(), size);
+ } else {
+ bool isDX10 = false;
+ DirectX::DDS_HEADER_DXT10 DX10HeaderData = {};
+ DirectX::DDS_HEADER DDSHeaderData = getDDSHeader(file, DX10HeaderData, isDX10);
+
+ outFile.write("DDS ", 4);
+ char* DDSHeader = new char[sizeof(DDSHeaderData)];
+ memcpy(DDSHeader, &DDSHeaderData, sizeof(DDSHeaderData));
+ outFile.write(DDSHeader, sizeof(DDSHeaderData));
+ delete[] DDSHeader;
+
+ if (isDX10) {
+ getDX10Header(DX10HeaderData, file, DDSHeaderData);
+ char* DX10Header = new char[sizeof(DX10HeaderData)];
+ memcpy(DX10Header, &DX10HeaderData, sizeof(DX10HeaderData));
+ outFile.write(DX10Header, sizeof(DX10HeaderData));
+ delete[] DX10Header;
+ }
+
+ for (BSAUInt i = 0; i < file->m_TextureChunks.size(); ++i) {
+ BSAULong length = file->m_TextureChunks[i].unpackedSize;
+ std::unique_ptr<char[]> chunk(new char[length]);
+ m_File.read(chunk.get(), length);
+ outFile.write(chunk.get(), length);
+ }
+ }
+ } else {
+ BSAULong size = file->m_FileSize;
+ if (namePrefixed()) {
+ std::string fullName = readBString(m_File);
+ if (size <= fullName.length()) {
+#pragma message("report error!")
+ return result;
+ }
+ size -= fullName.length() + 1;
+ }
+ std::unique_ptr<unsigned char[]> buffer(new unsigned char[size]);
+ m_File.read(reinterpret_cast<char*>(buffer.get()), size);
+ if (result == ERROR_NONE)
+ outFile.write(reinterpret_cast<char*>(buffer.get()), size);
+ }
+ std::unique_ptr<char[]> inBuffer(new char[CHUNK_SIZE]);
+
+ try {
+ unsigned long sizeLeft = file->m_FileSize;
+
+ while (sizeLeft > 0) {
+ int chunkSize = (std::min)(sizeLeft, CHUNK_SIZE);
+ m_File.read(inBuffer.get(), chunkSize);
+ outFile.write(inBuffer.get(), chunkSize);
+ sizeLeft -= chunkSize;
+ }
+ } catch (const std::exception&) {
+ result = ERROR_INVALIDDATA;
+ }
+ return result;
+}
+
+std::shared_ptr<unsigned char[]> Archive::decompress(unsigned char* inBuffer,
+ BSAULong inSize,
+ EErrorCode& result,
+ BSAULong& outSize)
+{
+ if (outSize == 0) {
+ memcpy(&outSize, inBuffer, sizeof(BSAULong));
+ inBuffer += sizeof(BSAULong);
+ inSize -= sizeof(BSAULong);
+ }
+
+ if ((inSize == 0) || (outSize == 0)) {
+ return std::shared_ptr<unsigned char[]>();
+ }
+
+ std::shared_ptr<unsigned char[]> outBuffer(new unsigned char[outSize]);
+
+ z_stream stream;
+ try {
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
+ stream.avail_in = inSize;
+ stream.next_in = static_cast<Bytef*>(inBuffer);
+ int zlibRet = inflateInit2(&stream, 15 + 32);
+ if (zlibRet != Z_OK) {
+ result = ERROR_ZLIBINITFAILED;
+ return std::shared_ptr<unsigned char[]>();
+ }
+
+ do {
+ stream.avail_out = outSize;
+ stream.next_out = reinterpret_cast<Bytef*>(outBuffer.get());
+ zlibRet = inflate(&stream, Z_NO_FLUSH);
+ if ((zlibRet != Z_OK) && (zlibRet != Z_STREAM_END) && (zlibRet != Z_BUF_ERROR)) {
+#pragma message("pass result code to caller")
+ throw std::runtime_error("invalid data");
+ }
+ } while (stream.avail_out == 0);
+ inflateEnd(&stream);
+ return outBuffer;
+ } catch (const std::exception&) {
+ result = ERROR_INVALIDDATA;
+ inflateEnd(&stream);
+ return std::shared_ptr<unsigned char[]>();
+ }
+}
+
+EErrorCode Archive::extractCompressed(File::Ptr file, std::ofstream& outFile) const
+{
+ EErrorCode result = ERROR_NONE;
+ if (file->m_FileSize == 0) {
+ // don't try to read empty file
+ return result;
+ }
+
+ m_File.clear();
+ m_File.seekg(static_cast<std::ifstream::pos_type>(file->m_DataOffset), std::ios::beg);
+
+ if (m_Type == TYPE_FALLOUT4 || m_Type == TYPE_STARFIELD ||
+ m_Type == TYPE_STARFIELD_LZ4_TEXTURE || m_Type == TYPE_FALLOUT4NG_7 ||
+ m_Type == TYPE_FALLOUT4NG_8) {
+ if (!file->m_TextureChunks.size()) {
+ BSAULong inSize = file->m_FileSize;
+ std::unique_ptr<unsigned char[]> inBuffer(new unsigned char[inSize]);
+ m_File.read(reinterpret_cast<char*>(inBuffer.get()), inSize);
+ BSAULong length = file->m_UncompressedFileSize;
+ std::shared_ptr<unsigned char[]> buffer =
+ decompress(inBuffer.get(), inSize, result, length);
+ if (result == ERROR_NONE) {
+ outFile.write(reinterpret_cast<char*>(buffer.get()), length);
+ }
+ } else {
+ bool isDX10 = false;
+ DirectX::DDS_HEADER_DXT10 DX10HeaderData = {};
+ DirectX::DDS_HEADER DDSHeaderData = getDDSHeader(file, DX10HeaderData, isDX10);
+
+ outFile.write("DDS ", 4);
+ char* DDSHeader = new char[sizeof(DDSHeaderData)];
+ memcpy(DDSHeader, &DDSHeaderData, sizeof(DDSHeaderData));
+ outFile.write(DDSHeader, sizeof(DDSHeaderData));
+ delete[] DDSHeader;
+
+ if (isDX10) {
+ getDX10Header(DX10HeaderData, file, DDSHeaderData);
+ char* DX10Header = new char[sizeof(DX10HeaderData)];
+ memcpy(DX10Header, &DX10HeaderData, sizeof(DX10HeaderData));
+ outFile.write(DX10Header, sizeof(DX10HeaderData));
+ delete[] DX10Header;
+ }
+
+ for (BSAUInt i = 0; i < file->m_TextureChunks.size(); ++i) {
+ BSAULong length = file->m_TextureChunks[i].unpackedSize;
+ if (file->m_TextureChunks[i].packedSize > 0) {
+ unsigned char* chunk = new unsigned char[file->m_TextureChunks[i].packedSize];
+ m_File.read(reinterpret_cast<char*>(chunk),
+ file->m_TextureChunks[i].packedSize);
+ if (m_Type == TYPE_STARFIELD_LZ4_TEXTURE) {
+ char* unpackedChunk = new char[length];
+ LZ4_decompress_safe(reinterpret_cast<char*>(chunk), unpackedChunk,
+ file->m_TextureChunks[i].packedSize, length);
+ outFile.write(unpackedChunk, length);
+ delete[] unpackedChunk;
+ } else {
+ std::shared_ptr<unsigned char[]> unpackedChunk =
+ decompress(chunk, file->m_TextureChunks[i].packedSize, result, length);
+ if (result == ERROR_NONE) {
+ outFile.write(reinterpret_cast<char*>(unpackedChunk.get()), length);
+ }
+ }
+ delete[] chunk;
+ } else {
+ char* chunk = new char[length];
+ m_File.read(chunk, length);
+ outFile.write(chunk, length);
+ delete[] chunk;
+ }
+ }
+ }
+ } else if (m_Type == TYPE_SKYRIMSE) {
+ BSAULong inSize = file->m_FileSize;
+ if (namePrefixed()) {
+ std::string fullName = readBString(m_File);
+ if (inSize <= fullName.length()) {
+#pragma message("report error!")
+ return result;
+ }
+ inSize -= fullName.length() + 1;
+ }
+ BSAULong outSize = readType<BSAULong>(m_File);
+ inSize -= sizeof(BSAULong);
+ std::unique_ptr<unsigned char[]> inBuffer(new unsigned char[inSize]);
+ m_File.read(reinterpret_cast<char*>(inBuffer.get()), inSize);
+
+ LZ4F_decompressionContext_t dcContext = nullptr;
+ LZ4F_decompressOptions_t options = {};
+ LZ4F_createDecompressionContext(&dcContext, LZ4F_VERSION);
+ size_t lzOutSize = outSize;
+ size_t lzInSize = inSize;
+
+ std::unique_ptr<unsigned char[]> outBuffer(new unsigned char[outSize]);
+ LZ4F_decompress(dcContext, outBuffer.get(), &lzOutSize, inBuffer.get(), &lzInSize,
+ &options);
+
+ outFile.write(reinterpret_cast<char*>(outBuffer.get()), outSize);
+ } else {
+ BSAULong inSize = file->m_FileSize;
+ if (namePrefixed()) {
+ std::string fullName = readBString(m_File);
+ if (inSize <= fullName.length()) {
+#pragma message("report error!")
+ return result;
+ }
+ inSize -= fullName.length() + 1;
+ }
+ std::unique_ptr<unsigned char[]> inBuffer(new unsigned char[inSize]);
+ m_File.read(reinterpret_cast<char*>(inBuffer.get()), inSize);
+ BSAULong length = 0UL;
+ std::shared_ptr<unsigned char[]> buffer =
+ decompress(inBuffer.get(), inSize, result, length);
+ if (result == ERROR_NONE) {
+ outFile.write(reinterpret_cast<char*>(buffer.get()), length);
+ }
+ }
+
+ return result;
+}
+
+EErrorCode Archive::extract(File::Ptr file, const char* outputDirectory) const
+{
+ std::string fileName = makeString("%s/%s", outputDirectory, file->getName().c_str());
+ std::ofstream outputFile(fileName.c_str(),
+ fstream::out | fstream::binary | fstream::trunc);
+ if (!outputFile.is_open()) {
+ return ERROR_ACCESSFAILED;
+ }
+
+ EErrorCode result = ERROR_NONE;
+ if (compressed(file)) {
+ result = extractCompressed(file, outputFile);
+ } else {
+ result = extractDirect(file, outputFile);
+ }
+ outputFile.close();
+ return result;
+}
+
+void Archive::readFiles(std::queue<FileInfo>& queue, boost::mutex& mutex,
+ boost::interprocess::interprocess_semaphore& bufferCount,
+ boost::interprocess::interprocess_semaphore& queueFree,
+ std::vector<File::Ptr>::iterator begin,
+ std::vector<File::Ptr>::iterator end)
+{
+ for (; begin != end && !boost::this_thread::interruption_requested(); ++begin) {
+ queueFree.wait();
+
+ FileInfo fileInfo;
+ fileInfo.file = *begin;
+ size_t size = static_cast<size_t>(fileInfo.file->m_FileSize);
+
+ m_File.seekg(fileInfo.file->m_DataOffset);
+ if (m_Type != TYPE_FALLOUT4 && m_Type != TYPE_STARFIELD &&
+ m_Type != TYPE_STARFIELD_LZ4_TEXTURE && m_Type != TYPE_FALLOUT4NG_7 &&
+ m_Type != TYPE_FALLOUT4NG_8) {
+ if (namePrefixed()) {
+ std::string fullName = readBString(m_File);
+ if (size <= fullName.length()) {
+#pragma message("report error!")
+ continue;
+ }
+ size -= fullName.length() + 1;
+ }
+ if (m_Type == TYPE_SKYRIMSE && compressed(fileInfo.file)) {
+ fileInfo.file->m_UncompressedFileSize = readType<BSAULong>(m_File);
+ size -= 4;
+ }
+ if (!fileInfo.file->m_TextureChunks.size()) {
+ fileInfo.data =
+ std::make_pair(std::shared_ptr<unsigned char[]>(new unsigned char[size]),
+ static_cast<BSAULong>(size));
+ m_File.read(reinterpret_cast<char*>(fileInfo.data.first.get()), size);
+ }
+ } else {
+ if (!fileInfo.file->m_TextureChunks.size()) {
+ if (size == 0)
+ size = fileInfo.file->m_UncompressedFileSize;
+ fileInfo.data =
+ std::make_pair(std::shared_ptr<unsigned char[]>(new unsigned char[size]),
+ static_cast<BSAULong>(size));
+ m_File.read(reinterpret_cast<char*>(fileInfo.data.first.get()), size);
+ } else {
+ fileInfo.file->m_UncompressedFileSize = 0L;
+ BSAULong totalSize = 0U;
+ for (BSAUInt i = 0; i < fileInfo.file->m_TextureChunks.size(); ++i) {
+ totalSize += fileInfo.file->m_TextureChunks[i].unpackedSize;
+ }
+ char* chunkData = new char[totalSize];
+ BSAULong currentPos = 0U;
+ for (BSAUInt i = 0; i < fileInfo.file->m_TextureChunks.size(); ++i) {
+ BSAULong length = fileInfo.file->m_TextureChunks[i].unpackedSize;
+ if (fileInfo.file->m_TextureChunks[i].packedSize > 0) {
+ char* chunk = new char[fileInfo.file->m_TextureChunks[i].packedSize];
+ m_File.read(chunk, fileInfo.file->m_TextureChunks[i].packedSize);
+ if (m_Type == TYPE_FALLOUT4 || m_Type == TYPE_STARFIELD ||
+ m_Type == TYPE_FALLOUT4NG_7 || m_Type == TYPE_FALLOUT4NG_8) {
+ EErrorCode result = ERROR_NONE;
+ try {
+ std::shared_ptr<unsigned char[]> unpackedChunk = decompress(
+ reinterpret_cast<unsigned char*>(chunk),
+ fileInfo.file->m_TextureChunks[i].packedSize, result, length);
+ memcpy(chunkData + currentPos,
+ reinterpret_cast<char*>(unpackedChunk.get()), length);
+ unpackedChunk.reset();
+ } catch (const std::exception&) {
+#pragma message("report error!")
+ continue;
+ }
+ } else {
+ char* unpackedChunk = new char[length];
+ LZ4_decompress_safe(chunk, unpackedChunk,
+ fileInfo.file->m_TextureChunks[i].packedSize, length);
+ memcpy(chunkData + currentPos, unpackedChunk, length);
+ delete[] unpackedChunk;
+ }
+ delete[] chunk;
+ fileInfo.file->m_UncompressedFileSize += length;
+ } else {
+ char* chunk = new char[length];
+ m_File.read(chunk, length);
+ memcpy(chunkData + currentPos, chunk, length);
+ delete[] chunk;
+ }
+ currentPos += length;
+ }
+ fileInfo.file->m_FileSize = 0;
+ fileInfo.data = std::make_pair(std::shared_ptr<unsigned char[]>(
+ reinterpret_cast<unsigned char*>(chunkData)),
+ static_cast<BSAULong>(totalSize));
+ }
+ }
+
+ {
+ boost::interprocess::scoped_lock<boost::mutex> lock(mutex);
+ queue.push(fileInfo);
+ }
+ bufferCount.post();
+ }
+}
+
+inline bool fileExists(const std::string& name)
+{
+ struct stat buffer;
+ return stat(name.c_str(), &buffer) != -1;
+}
+
+void Archive::extractFiles(const std::string& targetDirectory,
+ std::queue<FileInfo>& queue, boost::mutex& mutex,
+ boost::interprocess::interprocess_semaphore& bufferCount,
+ boost::interprocess::interprocess_semaphore& queueFree,
+ int totalFiles, bool overwrite, int& filesDone)
+{
+ for (int i = 0; i < totalFiles; ++i) {
+ bufferCount.wait();
+ if (boost::this_thread::interruption_requested()) {
+ break;
+ }
+
+ FileInfo fileInfo;
+
+ {
+ boost::interprocess::scoped_lock<boost::mutex> lock(mutex);
+ fileInfo = queue.front();
+ ++filesDone;
+ queue.pop();
+ }
+ queueFree.post();
+
+ DataBuffer dataBuffer = fileInfo.data;
+
+ std::string fileName = makeString("%s\\%s", targetDirectory.c_str(),
+ fileInfo.file->getFilePath().c_str());
+ if (!overwrite && fileExists(fileName)) {
+ continue;
+ }
+
+ std::ofstream outputFile(fileName.c_str(),
+ fstream::out | fstream::binary | fstream::trunc);
+
+ if (!outputFile.is_open()) {
+#pragma message("report error!")
+ continue;
+ // return ERROR_ACCESSFAILED;
+ }
+
+ if (m_Type != TYPE_FALLOUT4 && m_Type != TYPE_STARFIELD &&
+ m_Type != TYPE_STARFIELD_LZ4_TEXTURE && m_Type != TYPE_FALLOUT4NG_7 &&
+ m_Type != TYPE_FALLOUT4NG_8) {
+ // BSA extraction
+ if (compressed(fileInfo.file)) {
+ // Decompress data
+ if (m_Type != TYPE_SKYRIMSE) {
+ // Oblivion - Skyrim LE use gzip compression
+ EErrorCode result = ERROR_NONE;
+ try {
+ BSAULong length = 0UL;
+ std::shared_ptr<unsigned char[]> buffer =
+ decompress(dataBuffer.first.get(), dataBuffer.second, result, length);
+ if (buffer.get() != nullptr) {
+ outputFile.write(reinterpret_cast<char*>(buffer.get()), length);
+ buffer.reset();
+ }
+ } catch (const std::exception&) {
+#pragma message("report error!")
+ dataBuffer.first.reset();
+ fileInfo.data.first.reset();
+ continue;
+ }
+ } else {
+ // Skyrim SE uses LZ4 Frame compression
+ if (!fileInfo.file->m_TextureChunks.size()) {
+ char* outBuffer = new char[fileInfo.file->m_UncompressedFileSize];
+
+ LZ4F_decompressionContext_t dcContext = nullptr;
+ LZ4F_decompressOptions_t options = {};
+ LZ4F_createDecompressionContext(&dcContext, LZ4F_VERSION);
+ size_t outSize = fileInfo.file->m_UncompressedFileSize;
+ size_t inSize = dataBuffer.second;
+
+ LZ4F_decompress(dcContext, outBuffer, &outSize, dataBuffer.first.get(),
+ &inSize, &options);
+ outputFile.write(outBuffer, fileInfo.file->m_UncompressedFileSize);
+ LZ4F_freeDecompressionContext(dcContext);
+ delete[] outBuffer;
+ }
+ }
+ } else {
+ // No compression - just write the data.
+ outputFile.write(reinterpret_cast<char*>(dataBuffer.first.get()),
+ dataBuffer.second);
+ }
+ } else {
+ // BA2 format
+ if (fileInfo.file->m_TextureChunks.size()) {
+ // Texture stream format - requires building the header data for the DDS file
+ bool isDX10 = false;
+ DirectX::DDS_HEADER_DXT10 DX10HeaderData = {};
+ DirectX::DDS_HEADER DDSHeaderData =
+ getDDSHeader(fileInfo.file, DX10HeaderData, isDX10);
+
+ outputFile.write("DDS ", 4);
+ char* DDSHeader = new char[sizeof(DDSHeaderData)];
+ memcpy(DDSHeader, &DDSHeaderData, sizeof(DDSHeaderData));
+ outputFile.write(DDSHeader, sizeof(DDSHeaderData));
+ delete[] DDSHeader;
+
+ if (isDX10) {
+ // This format requires DX10 header info
+ getDX10Header(DX10HeaderData, fileInfo.file, DDSHeaderData);
+
+ char* DX10Header = new char[sizeof(DX10HeaderData)];
+ memcpy(DX10Header, &DX10HeaderData, sizeof(DX10HeaderData));
+ outputFile.write(DX10Header, sizeof(DX10HeaderData));
+ delete[] DX10Header;
+ }
+ }
+
+ EErrorCode result = ERROR_NONE;
+ try {
+ if (fileInfo.file->m_FileSize > 0 && !fileInfo.file->m_TextureChunks.size()) {
+ BSAULong length = fileInfo.file->m_UncompressedFileSize;
+ std::shared_ptr<unsigned char[]> buffer =
+ decompress(dataBuffer.first.get(), dataBuffer.second, result, length);
+ if (buffer.get() != nullptr) {
+ outputFile.write(reinterpret_cast<char*>(buffer.get()), length);
+ buffer.reset();
+ }
+ } else {
+ outputFile.write(reinterpret_cast<char*>(dataBuffer.first.get()),
+ dataBuffer.second);
+ }
+ } catch (const std::exception&) {
+#pragma message("report error!")
+ dataBuffer.first.reset();
+ fileInfo.data.first.reset();
+ continue;
+ }
+ }
+ dataBuffer.first.reset();
+ fileInfo.data.first.reset();
+ }
+}
+
+void Archive::createFolders(const std::string& targetDirectory, Folder::Ptr folder)
+{
+ for (std::vector<Folder::Ptr>::iterator iter = folder->m_SubFolders.begin();
+ iter != folder->m_SubFolders.end(); ++iter) {
+ std::string subDirName = targetDirectory + "/" + (*iter)->getName();
+#ifdef _WIN32
+ ::CreateDirectoryA(subDirName.c_str(), nullptr);
+#else
+ mkdir(subDirName.c_str(), 0755);
+#endif
+ createFolders(subDirName, *iter);
+ }
+}
+
+EErrorCode Archive::extractAll(
+ const char* outputDirectory,
+ const std::function<bool(int value, std::string fileName)>& progress,
+ bool overwrite)
+{
+#pragma message("report errors")
+ createFolders(outputDirectory, m_RootFolder);
+
+ std::vector<File::Ptr> fileList;
+ m_RootFolder->collectFiles(fileList);
+ std::sort(fileList.begin(), fileList.end(), ByOffset);
+ m_File.seekg((*(fileList.begin()))->m_DataOffset);
+
+ std::queue<FileInfo> buffers;
+ boost::mutex queueMutex;
+ int filesDone = 0;
+ boost::interprocess::interprocess_semaphore bufferCount(0);
+ boost::interprocess::interprocess_semaphore queueFree(100);
+
+ boost::thread readerThread(boost::bind(&Archive::readFiles, this, boost::ref(buffers),
+ boost::ref(queueMutex),
+ boost::ref(bufferCount), boost::ref(queueFree),
+ fileList.begin(), fileList.end()));
+
+ boost::thread extractThread(boost::bind(
+ &Archive::extractFiles, this, outputDirectory, boost::ref(buffers),
+ boost::ref(queueMutex), boost::ref(bufferCount), boost::ref(queueFree),
+ static_cast<int>(fileList.size()), overwrite, boost::ref(filesDone)));
+
+ bool readerDone = false;
+ bool extractDone = false;
+ bool canceled = false;
+ while (!readerDone || !extractDone) {
+ if (!readerDone) {
+ readerDone = readerThread.timed_join(boost::posix_time::millisec(100));
+ }
+ if (readerDone) {
+ extractDone = extractThread.timed_join(boost::posix_time::millisec(100));
+ // don't cancel extractor before reader is done or else reader may be stuck trying
+ // to write to a queue
+ if (canceled) {
+ // ensure the extract thread wakes up.
+ extractThread.interrupt();
+ bufferCount.post();
+ }
+ }
+ size_t index = (std::min)(static_cast<size_t>(filesDone), fileList.size() - 1);
+ if (!progress((filesDone * 100) / static_cast<int>(fileList.size()),
+ fileList[index]->getName()) &&
+ !canceled) {
+ readerThread.interrupt();
+ canceled = true; // don't interrupt repeatedly
+ }
+ }
+
+ return ERROR_NONE;
+}
+
+bool Archive::compressed(const File::Ptr& file) const
+{
+ if (m_Type != TYPE_FALLOUT4 && m_Type != TYPE_FALLOUT4NG_7 &&
+ m_Type != TYPE_FALLOUT4NG_8)
+ return file->compressToggled() ^ defaultCompressed();
+ return (file->m_FileSize > 0);
+}
+
+File::Ptr Archive::createFile(const std::string& name, const std::string& sourceName,
+ bool compressed)
+{
+ return File::Ptr(
+ new File(name, sourceName, nullptr, defaultCompressed() != compressed));
+}
+
+void Archive::cleanFolder(Folder::Ptr folder)
+{
+ std::vector<Folder::Ptr> folders;
+ folder->collectFolders(folders);
+ for (Folder::Ptr subFolder : folders) {
+ cleanFolder(subFolder);
+ }
+ std::vector<File::Ptr> files;
+ folder->collectFiles(files);
+ for (File::Ptr file : files) {
+ file.reset();
+ }
+ folder.reset();
+}
+
+} // namespace BSA