aboutsummaryrefslogtreecommitdiff
path: root/libs/bsatk/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/bsatk/src')
-rw-r--r--libs/bsatk/src/CMakeLists.txt83
-rw-r--r--libs/bsatk/src/bsaarchive.cpp1237
-rw-r--r--libs/bsatk/src/bsaexception.cpp39
-rw-r--r--libs/bsatk/src/bsafile.cpp154
-rw-r--r--libs/bsatk/src/bsafolder.cpp342
-rw-r--r--libs/bsatk/src/bsatypes.cpp70
-rw-r--r--libs/bsatk/src/filehash.cpp100
7 files changed, 2025 insertions, 0 deletions
diff --git a/libs/bsatk/src/CMakeLists.txt b/libs/bsatk/src/CMakeLists.txt
new file mode 100644
index 0000000..99a0f26
--- /dev/null
+++ b/libs/bsatk/src/CMakeLists.txt
@@ -0,0 +1,83 @@
+cmake_minimum_required(VERSION 3.16)
+
+find_package(Boost REQUIRED COMPONENTS thread)
+find_package(ZLIB REQUIRED)
+find_package(mo2-dds-header CONFIG REQUIRED)
+
+# lz4: try CONFIG first, fall back to pkg-config
+find_package(lz4 CONFIG QUIET)
+if (NOT lz4_FOUND)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(LZ4 REQUIRED IMPORTED_TARGET liblz4)
+endif()
+# lz4 1.10+ exports LZ4::lz4, older versions export lz4::lz4
+if(TARGET LZ4::lz4 AND NOT TARGET lz4::lz4)
+ add_library(lz4::lz4 ALIAS LZ4::lz4)
+endif()
+
+add_library(bsatk STATIC)
+target_link_libraries(bsatk
+ PUBLIC mo2::dds-header
+ PRIVATE ZLIB::ZLIB Boost::thread
+)
+
+# Link lz4 based on how it was found
+if (lz4_FOUND)
+ target_link_libraries(bsatk PRIVATE lz4::lz4)
+else()
+ target_link_libraries(bsatk PRIVATE PkgConfig::LZ4)
+endif()
+
+target_sources(bsatk
+ PRIVATE
+ bsaarchive.cpp
+ bsaexception.cpp
+ bsafile.cpp
+ bsafolder.cpp
+ bsatypes.cpp
+ filehash.cpp
+ PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/../include
+ FILES
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsaarchive.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsaexception.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsafile.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsafolder.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsatk.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/bsatypes.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/errorcodes.h
+ ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk/filehash.h
+
+)
+
+set_target_properties(bsatk PROPERTIES CXX_STANDARD 20)
+
+# for building
+target_include_directories(bsatk PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../include/bsatk)
+
+if (MSVC)
+ target_compile_options(bsatk
+ PRIVATE
+ "/MP"
+ "/W4"
+ "/external:anglebrackets"
+ "/external:W0"
+ )
+ target_link_options(bsatk
+ PRIVATE
+ $<$<CONFIG:RelWithDebInfo>:/LTCG /INCREMENTAL:NO /OPT:REF /OPT:ICF>
+ )
+
+ set_target_properties(bsatk PROPERTIES VS_STARTUP_PROJECT bsatk)
+endif()
+
+add_library(mo2::bsatk ALIAS bsatk)
+
+# install
+install(TARGETS bsatk EXPORT bsatkTargets FILE_SET HEADERS)
+install(EXPORT bsatkTargets
+ FILE mo2-bsatk-targets.cmake
+ NAMESPACE mo2::
+ DESTINATION lib/cmake/mo2-bsatk
+)
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
diff --git a/libs/bsatk/src/bsaexception.cpp b/libs/bsatk/src/bsaexception.cpp
new file mode 100644
index 0000000..9422848
--- /dev/null
+++ b/libs/bsatk/src/bsaexception.cpp
@@ -0,0 +1,39 @@
+/*
+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 "bsaexception.h"
+
+#include <cstdarg>
+#include <cstdio>
+
+#pragma warning(disable : 4996)
+
+std::string makeString(const char* format, ...)
+{
+ va_list argList;
+ va_start(argList, format);
+ char buffer[1024];
+ vsnprintf(buffer, 1024, format, argList);
+ return std::string(buffer);
+}
+
+data_invalid_exception::data_invalid_exception(const std::string& message)
+ : m_Message(message)
+{}
diff --git a/libs/bsatk/src/bsafile.cpp b/libs/bsatk/src/bsafile.cpp
new file mode 100644
index 0000000..db4e4e6
--- /dev/null
+++ b/libs/bsatk/src/bsafile.cpp
@@ -0,0 +1,154 @@
+/*
+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 "bsafile.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+#include <memory>
+#include <stdexcept>
+
+#include "bsaexception.h"
+#include "bsafolder.h"
+#include "filehash.h"
+
+using std::fstream;
+using std::ifstream;
+using std::ofstream;
+
+namespace BSA
+{
+
+bool ByOffset(const File::Ptr& LHS, const File::Ptr& RHS)
+{
+ return LHS->getDataOffset() < RHS->getDataOffset();
+}
+
+static const unsigned long CHUNK_SIZE = 128 * 1024;
+
+File::File(std::fstream& file, Folder* folder)
+ : m_Folder(folder), m_New(false), m_FileSize(0), m_UncompressedFileSize(0),
+ m_ToggleCompressedWrite(false), m_DataOffsetWrite(0)
+{
+ m_NameHash = readType<BSAHash>(file);
+ m_FileSize = readType<BSAULong>(file);
+ m_DataOffset = readType<BSAULong>(file);
+ m_ToggleCompressed = m_FileSize & COMPRESSMASK;
+ m_FileSize = m_FileSize & SIZEMASK;
+}
+
+File::File(const std::string& name, Folder* folder, BSAULong fileSize,
+ BSAHash dataOffset, BSAULong uncompressedFileSize, FO4TextureHeader header,
+ std::vector<FO4TextureChunk>& texChunks)
+ : m_Folder(folder), m_New(false), m_Name(name), m_FileSize(fileSize),
+ m_UncompressedFileSize(uncompressedFileSize), m_DataOffset(dataOffset),
+ m_TextureHeader(header), m_ToggleCompressedWrite(false),
+ m_TextureChunks(texChunks), m_DataOffsetWrite(0)
+{
+ m_NameHash = calculateBSAHash(name);
+ m_ToggleCompressed = false;
+ if (m_FileSize > 0 && m_UncompressedFileSize > 0)
+ m_ToggleCompressed = true;
+}
+
+File::File(const std::string& name, const std::string& sourceFile, Folder* folder,
+ bool toggleCompressed)
+ : m_Folder(folder), m_New(true), m_Name(name), m_FileSize(0),
+ m_UncompressedFileSize(0), m_DataOffset(0), m_ToggleCompressed(toggleCompressed),
+ m_SourceFile(sourceFile), m_ToggleCompressedWrite(toggleCompressed),
+ m_DataOffsetWrite(0)
+{
+ m_NameHash = calculateBSAHash(name);
+}
+
+std::string File::getFilePath() const
+{
+ return m_Folder->getFullPath() + "/" + m_Name;
+}
+
+void File::writeHeader(fstream& file) const
+{
+ writeType<BSAHash>(file, m_NameHash);
+ BSAULong size = m_FileSize;
+ if (m_ToggleCompressed) {
+ size |= (1 << 30);
+ }
+ writeType<BSAULong>(file, size);
+ writeType<BSAULong>(file, m_DataOffsetWrite);
+}
+
+EErrorCode File::writeData(fstream& sourceArchive, fstream& targetArchive) const
+{
+ m_DataOffsetWrite = static_cast<BSAULong>(targetArchive.tellp());
+ EErrorCode result = ERROR_NONE;
+
+ std::unique_ptr<char[]> inBuffer(new char[CHUNK_SIZE]);
+
+ if (m_SourceFile.length() == 0) {
+ // copy from source archive
+#pragma message("we may have to compress/decompress!")
+ sourceArchive.seekg(m_DataOffset, fstream::beg);
+
+ try {
+ unsigned long sizeLeft = m_FileSize;
+ while (sizeLeft > 0) {
+ int chunkSize = (std::min)(sizeLeft, CHUNK_SIZE);
+ sourceArchive.read(inBuffer.get(), chunkSize);
+ targetArchive.write(inBuffer.get(), chunkSize);
+ sizeLeft -= chunkSize;
+ }
+ } catch (const std::exception&) {
+ result = ERROR_INVALIDDATA;
+ }
+ } else {
+ // copy from file on disc
+ fstream sourceFile;
+ sourceFile.open(m_SourceFile.c_str());
+ if (!sourceFile.is_open()) {
+ return ERROR_SOURCEFILEMISSING;
+ }
+ sourceFile.seekg(0, fstream::end);
+ m_FileSize = static_cast<BSAULong>(sourceFile.tellg());
+ unsigned long sizeLeft = m_FileSize;
+ sourceFile.seekg(0, fstream::beg);
+ while (sizeLeft > 0) {
+ int chunkSize = (std::min)(sizeLeft, CHUNK_SIZE);
+ sourceFile.read(inBuffer.get(), chunkSize);
+ targetArchive.write(inBuffer.get(), chunkSize);
+ sizeLeft -= chunkSize;
+ }
+ }
+ return result;
+}
+
+void File::readFileName(fstream& file, bool testHashes)
+{
+ m_Name = readZString(file);
+ if (testHashes) {
+ if (calculateBSAHash(m_Name) != m_NameHash) {
+ throw data_invalid_exception(
+ makeString("invalid name hash for \"%s\" (%lx vs %lx)", m_Name.c_str(),
+ calculateBSAHash(m_Name), m_NameHash));
+ }
+ }
+}
+
+} // namespace BSA
diff --git a/libs/bsatk/src/bsafolder.cpp b/libs/bsatk/src/bsafolder.cpp
new file mode 100644
index 0000000..21e2611
--- /dev/null
+++ b/libs/bsatk/src/bsafolder.cpp
@@ -0,0 +1,342 @@
+/*
+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 <filesystem>
+#include <limits.h>
+
+#include "bsaarchive.h"
+#include "bsaexception.h"
+#include "bsafile.h"
+#include "bsafolder.h"
+
+using std::fstream;
+
+namespace BSA
+{
+
+Folder::Folder() : m_Parent(nullptr), m_Name()
+{
+ m_NameHash = calculateBSAHash(m_Name);
+ m_FileCount = 0;
+ m_Offset = ULONG_MAX;
+}
+
+Folder::Ptr Folder::readFolder(std::fstream& file, BSAUInt fileNamesLength,
+ BSAUInt& endPos)
+{
+ Folder::Ptr result(new Folder());
+ result->m_NameHash = readType<BSAHash>(file);
+ result->m_FileCount = readType<unsigned long>(file);
+ result->m_Offset = readType<unsigned long>(file);
+ std::streamoff pos = file.tellg();
+
+ file.seekg(result->m_Offset - fileNamesLength, fstream::beg);
+
+ result->m_Name = readBString(file);
+
+ for (unsigned long i = 0UL; i < result->m_FileCount; ++i) {
+ result->m_Files.push_back(File::Ptr(new File(file, result.get())));
+ }
+
+ if (static_cast<unsigned long>(file.tellg()) > endPos) {
+ endPos = static_cast<BSAULong>(file.tellg());
+ }
+
+ file.seekg(pos);
+
+ return result;
+}
+
+Folder::Ptr Folder::readFolderSE(std::fstream& file, BSAUInt fileNamesLength,
+ BSAUInt& endPos)
+{
+ Folder::Ptr result(new Folder());
+ result->m_NameHash = readType<BSAHash>(file);
+ result->m_FileCount = readType<BSAUInt>(file);
+ readType<BSAUInt>(file);
+ result->m_Offset = readType<BSAHash>(file);
+ std::streamoff pos = file.tellg();
+
+ file.seekg(result->m_Offset - fileNamesLength, fstream::beg);
+
+ result->m_Name = readBString(file);
+
+ for (unsigned long i = 0UL; i < result->m_FileCount; ++i) {
+ result->m_Files.push_back(File::Ptr(new File(file, result.get())));
+ }
+
+ if (static_cast<unsigned long>(file.tellg()) > endPos) {
+ endPos = static_cast<BSAULong>(file.tellg());
+ }
+
+ file.seekg(pos);
+
+ return result;
+}
+
+void Folder::writeHeader(std::fstream& file) const
+{
+ writeType<BSAHash>(file, m_NameHash);
+ writeType<BSAULong>(file, static_cast<BSAULong>(m_Files.size()));
+ writeType<BSAULong>(file, m_OffsetWrite);
+}
+
+void Folder::writeData(std::fstream& file, BSAULong fileNamesLength) const
+{
+ m_OffsetWrite = static_cast<BSAULong>(file.tellp()) + fileNamesLength;
+ writeBString(file, getFullPath());
+ for (std::vector<File::Ptr>::const_iterator iter = m_Files.begin();
+ iter != m_Files.end(); ++iter) {
+ (*iter)->writeHeader(file);
+ }
+}
+
+EErrorCode Folder::writeFileData(std::fstream& sourceFile,
+ std::fstream& targetFile) const
+{
+ for (std::vector<File::Ptr>::const_iterator iter = m_Files.begin();
+ iter != m_Files.end(); ++iter) {
+ EErrorCode error = (*iter)->writeData(sourceFile, targetFile);
+ if (error != ERROR_NONE) {
+ return error;
+ }
+ }
+ return ERROR_NONE;
+}
+
+std::string Folder::getFullPath() const
+{
+ if (m_Parent != nullptr) {
+ std::string temp = m_Parent->getFullPath();
+ if (temp.length() != 0) {
+ return temp.append("\\").append(m_Name);
+ } else {
+ return m_Name;
+ }
+ } else {
+ // root folder shouldn't have a name
+ return std::string();
+ }
+}
+
+void Folder::addFolderInt(Folder::Ptr folder)
+{
+ std::filesystem::path path(folder->m_Name);
+ auto it = path.begin();
+ std::string firstStr = it->string();
+ std::filesystem::path remaining;
+ for (++it; it != path.end(); ++it) {
+ remaining /= *it;
+ }
+
+ if (m_SubFoldersByName.contains(firstStr)) {
+ // remove the matched part of the path and recurse
+ folder->m_Name = remaining.string();
+ m_SubFoldersByName.at(firstStr)->addFolderInt(folder);
+ return;
+ }
+
+ // no subfolder matches, create one
+ if (remaining.empty()) {
+ // no more path components, add the new folder right here
+ folder->m_Parent = this;
+ m_SubFolders.push_back(folder);
+ m_SubFoldersByName[firstStr] = folder;
+ } else {
+ // add dummy folder for the next path component
+ Folder::Ptr dummy(new Folder);
+ dummy->m_Parent = this;
+ dummy->m_Name = firstStr;
+ folder->m_Name = remaining.string();
+ dummy->addFolderInt(folder);
+ m_SubFolders.push_back(dummy);
+ m_SubFoldersByName[firstStr] = dummy;
+ }
+}
+
+Folder::Ptr Folder::addOrFindFolderInt(Folder* folder)
+{
+ std::filesystem::path path(folder->m_Name);
+ auto it = path.begin();
+ std::string firstStr = it->string();
+ std::filesystem::path remaining;
+ for (++it; it != path.end(); ++it) {
+ remaining /= *it;
+ }
+
+ if (m_SubFoldersByName.contains(firstStr)) {
+ if (!remaining.empty()) {
+ // remove the matched part of the path and recurse
+ folder->m_Name = remaining.string();
+ return m_SubFoldersByName.at(firstStr)->addOrFindFolderInt(folder);
+ } else {
+ return m_SubFoldersByName.at(firstStr);
+ }
+ }
+
+ // no subfolder matches, create one
+ if (remaining.empty()) {
+ // no more path components, add the new folder right here
+ folder->m_Parent = this;
+ m_SubFolders.push_back(Folder::Ptr(folder));
+ m_SubFoldersByName[firstStr] = m_SubFolders.back();
+ return m_SubFolders.back();
+ } else {
+ // add dummy folder for the next path component
+ Folder::Ptr dummy(new Folder);
+ dummy->m_Parent = this;
+ dummy->m_Name = firstStr;
+ folder->m_Name = remaining.string();
+ Folder::Ptr result = dummy->addOrFindFolderInt(folder);
+ m_SubFolders.push_back(dummy);
+ m_SubFoldersByName[firstStr] = dummy;
+ return result;
+ }
+}
+
+Folder::Ptr Folder::addFolder(std::fstream& file, BSAUInt fileNamesLength,
+ BSAUInt& endPos, ArchiveType type)
+{
+ Folder::Ptr temp;
+ if (type == ArchiveType::TYPE_SKYRIMSE)
+ temp = readFolderSE(file, fileNamesLength, endPos);
+ else
+ temp = readFolder(file, fileNamesLength, endPos);
+ addFolderInt(temp);
+
+ return temp;
+}
+
+Folder::Ptr Folder::addFolderFromFile(std::string filePath, BSAUInt size,
+ BSAHash offset, BSAUInt uncompressedSize,
+ FO4TextureHeader header,
+ std::vector<FO4TextureChunk>& texChunks)
+{
+ std::filesystem::path file(filePath);
+
+ Folder* tempFolder = new Folder();
+
+ tempFolder->m_NameHash = calculateBSAHash(filePath);
+ tempFolder->m_Name = file.parent_path().string();
+ Folder::Ptr result = addOrFindFolderInt(tempFolder);
+
+ if (result.get() != tempFolder) {
+ delete tempFolder;
+ }
+
+ std::string fileName = file.filename().string();
+
+ result->m_FileCount++;
+ result->m_Files.push_back(File::Ptr(new File(fileName, result.get(), size, offset,
+ uncompressedSize, header, texChunks)));
+
+ return result;
+}
+
+bool Folder::resolveFileNames(std::fstream& file, bool testHashes)
+{
+ bool hashesValid = true;
+ for (std::vector<File::Ptr>::iterator iter = m_Files.begin(); iter != m_Files.end();
+ ++iter) {
+ try {
+ (*iter)->readFileName(file, testHashes);
+ } catch (const std::exception&) {
+ hashesValid = false;
+ }
+ }
+ return hashesValid;
+}
+
+const Folder::Ptr Folder::getSubFolder(unsigned int index) const
+{
+ return m_SubFolders.at(index);
+}
+
+unsigned int Folder::countFiles() const
+{
+ unsigned int result = 0;
+ for (std::vector<Folder::Ptr>::const_iterator iter = m_SubFolders.begin();
+ iter != m_SubFolders.end(); ++iter) {
+ result += (*iter)->countFiles();
+ }
+ return result + static_cast<unsigned int>(m_Files.size());
+}
+
+const File::Ptr Folder::getFile(unsigned int index) const
+{
+ return m_Files.at(index);
+}
+
+Folder::Ptr Folder::addFolder(const std::string& folderName)
+{
+ Folder::Ptr newFolder(new Folder);
+ newFolder->m_Name = folderName;
+ newFolder->m_Parent = this;
+ m_SubFolders.push_back(newFolder);
+ return newFolder;
+}
+
+void Folder::collectFolders(std::vector<Folder::Ptr>& folderList) const
+{
+ for (std::vector<Folder::Ptr>::const_iterator iter = m_SubFolders.begin();
+ iter != m_SubFolders.end(); ++iter) {
+ if ((*iter)->m_Files.size() != 0) {
+ folderList.push_back(*iter);
+ }
+ (*iter)->collectFolders(folderList);
+ }
+}
+
+void Folder::collectFiles(std::vector<File::Ptr>& fileList) const
+{
+ for (std::vector<File::Ptr>::const_iterator fileIter = m_Files.begin();
+ fileIter != m_Files.end(); ++fileIter) {
+ fileList.push_back(*fileIter);
+ }
+ for (std::vector<Folder::Ptr>::const_iterator folderIter = m_SubFolders.begin();
+ folderIter != m_SubFolders.end(); ++folderIter) {
+ (*folderIter)->collectFiles(fileList);
+ }
+}
+
+void Folder::collectFileNames(std::vector<std::string>& nameList) const
+{
+ for (std::vector<File::Ptr>::const_iterator iter = m_Files.begin();
+ iter != m_Files.end(); ++iter) {
+ nameList.push_back((*iter)->getName());
+ }
+ for (std::vector<Folder::Ptr>::const_iterator iter = m_SubFolders.begin();
+ iter != m_SubFolders.end(); ++iter) {
+ (*iter)->collectFileNames(nameList);
+ }
+}
+
+void Folder::collectFolderNames(std::vector<std::string>& nameList) const
+{
+ if (m_Files.size() != 0) {
+ nameList.push_back(getFullPath());
+ }
+ for (std::vector<Folder::Ptr>::const_iterator iter = m_SubFolders.begin();
+ iter != m_SubFolders.end(); ++iter) {
+ (*iter)->collectFolderNames(nameList);
+ }
+}
+
+} // namespace BSA
diff --git a/libs/bsatk/src/bsatypes.cpp b/libs/bsatk/src/bsatypes.cpp
new file mode 100644
index 0000000..eb33af3
--- /dev/null
+++ b/libs/bsatk/src/bsatypes.cpp
@@ -0,0 +1,70 @@
+/*
+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 "bsatypes.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+#include <fstream>
+#include <string>
+
+using std::fstream;
+
+/*#ifndef MAX_PATH
+#define MAX_PATH PATH_MAX
+#endif // MAX_PATH
+*/
+
+std::string readBString(fstream& file)
+{
+ unsigned char length = readType<unsigned char>(file);
+ char buffer[256];
+ if (length > 0) {
+ if (!file.read(buffer, length)) {
+ throw data_invalid_exception("can't read from bsa");
+ }
+ }
+ buffer[length] = '\0';
+ return std::string(buffer);
+}
+
+void writeBString(fstream& file, const std::string& string)
+{
+ unsigned int length =
+ std::min<unsigned int>(static_cast<unsigned int>(string.length()), 255);
+ writeType<unsigned char>(file, length + 1);
+ file.write(string.c_str(), length + 1);
+}
+
+std::string readZString(fstream& file)
+{
+ char buffer[FILENAME_MAX];
+ memset(buffer, '\0', FILENAME_MAX);
+ if (!file.getline(buffer, FILENAME_MAX, '\0')) {
+ throw data_invalid_exception("can't read from bsa");
+ }
+ return std::string(buffer);
+}
+
+void writeZString(fstream& file, const std::string& string)
+{
+ file.write(string.c_str(), string.length() + 1);
+}
diff --git a/libs/bsatk/src/filehash.cpp b/libs/bsatk/src/filehash.cpp
new file mode 100644
index 0000000..717a02c
--- /dev/null
+++ b/libs/bsatk/src/filehash.cpp
@@ -0,0 +1,100 @@
+/*
+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 "filehash.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstdlib>
+#include <cstring>
+
+#ifndef MAX_PATH
+#define MAX_PATH PATH_MAX
+#endif // MAX_PATH
+
+static unsigned long genHashInt(const unsigned char* pos, const unsigned char* end)
+{
+ unsigned long hash = 0;
+ for (; pos < end; ++pos) {
+ hash *= 0x1003f;
+ hash += *pos;
+ }
+ return hash;
+}
+
+/**
+ * @brief calculateBSAHash
+ * @param fileName
+ * @return
+ * @note the hash calculated for folders seem to be wrong
+ */
+BSAHash calculateBSAHash(const std::string& fileName)
+{
+ char fileNameLower[FILENAME_MAX + 1];
+ int i = 0;
+ for (; i < FILENAME_MAX && fileName[i] != '\0'; ++i) {
+ fileNameLower[i] = tolower(fileName[i]);
+ if (fileNameLower[i] == '/') {
+ fileNameLower[i] = '\\';
+ }
+ }
+ fileNameLower[i] = '\0';
+
+ unsigned char* fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower);
+
+ size_t length = strlen(fileNameLower);
+
+ char* ext = strrchr(fileNameLower, '.');
+ if (ext == nullptr) {
+ ext = fileNameLower + length;
+ }
+
+ size_t extLen = strlen(ext);
+ length -= extLen;
+
+ unsigned char* extU = reinterpret_cast<unsigned char*>(ext);
+
+ BSAHash hash1 = 0ULL;
+
+ if (length > 0) {
+ hash1 = static_cast<BSAHash>(fileNameLowerU[length - 1] |
+ ((length > 2 ? fileNameLowerU[length - 2] : 0) << 8) |
+ (length << 16) | (fileNameLowerU[0] << 24));
+ }
+
+ if (extLen > 0) {
+ if (strcmp(ext + 1, "kf") == 0) {
+ hash1 |= 0x80;
+ } else if (strcmp(ext + 1, "nif") == 0) {
+ hash1 |= 0x8000;
+ } else if (strcmp(ext + 1, "dds") == 0) {
+ hash1 |= 0x8080;
+ } else if (strcmp(ext + 1, "wav") == 0) {
+ hash1 |= 0x80000000;
+ }
+
+ BSAHash hash2 = static_cast<BSAHash>(genHashInt(fileNameLowerU + 1, extU - 2)) +
+ static_cast<BSAHash>(genHashInt(extU, extU + strlen(ext)));
+
+ hash1 |= (hash2 & 0xFFFFFFFF) << 32;
+ }
+
+ return hash1;
+}