/// Program to test .nif files both on the FileSystem and in BSA archives. #include <exception> #include <filesystem> #include <iostream> #include <memory> #include <string> #include <utility> #include <vector> #include <components/files/configurationmanager.hpp> #include <components/files/constrainedfilestream.hpp> #include <components/files/conversion.hpp> #include <components/misc/strings/algorithm.hpp> #include <components/nif/niffile.hpp> #include <components/vfs/archive.hpp> #include <components/vfs/bsaarchive.hpp> #include <components/vfs/filesystemarchive.hpp> #include <components/vfs/manager.hpp> #include <boost/program_options.hpp> // Create local aliases for brevity namespace bpo = boost::program_options; /// See if the file has the named extension bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) { const auto extension = Files::pathToUnicodeString(filename.extension()); return Misc::StringUtils::ciEqual(extension, extensionToFind); } /// See if the file has the "nif" extension. bool isNIF(const std::filesystem::path& filename) { return hasExtension(filename, ".nif"); } /// See if the file has the "bsa" extension. bool isBSA(const std::filesystem::path& filename) { return hasExtension(filename, ".bsa"); } std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path) { switch (Bsa::BSAFile::detectVersion(path)) { case Bsa::BSAVER_UNKNOWN: std::cerr << '"' << path << "\" is unknown BSA archive" << std::endl; return nullptr; case Bsa::BSAVER_COMPRESSED: return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_COMPRESSED>::type>(path); case Bsa::BSAVER_BA2_GNRL: return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_GNRL>::type>(path); case Bsa::BSAVER_BA2_DX10: return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_DX10>::type>(path); case Bsa::BSAVER_UNCOMPRESSED: return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED>::type>(path); } std::cerr << '"' << path << "\" is unsupported BSA archive" << std::endl; return nullptr; } std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path) { if (isBSA(path)) return makeBsaArchive(path); if (std::filesystem::is_directory(path)) return std::make_unique<VFS::FileSystemArchive>(path); return nullptr; } /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. void readVFS(std::unique_ptr<VFS::Archive>&& anArchive, const std::filesystem::path& archivePath = {}) { if (anArchive == nullptr) return; VFS::Manager myManager; myManager.addArchive(std::move(anArchive)); myManager.buildIndex(); for (const auto& name : myManager.getRecursiveDirectoryIterator("")) { try { if (isNIF(name)) { // std::cout << "Decoding: " << name << std::endl; Nif::NIFFile file(archivePath / name); Nif::Reader reader(file); reader.parse(myManager.get(name)); } else if (isBSA(name)) { if (!archivePath.empty() && !isBSA(archivePath)) { // std::cout << "Reading BSA File: " << name << std::endl; readVFS(makeBsaArchive(archivePath / name), archivePath / name); // std::cout << "Done with BSA File: " << name << std::endl; } } } catch (std::exception& e) { std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; } } } bool parseOptions(int argc, char** argv, std::vector<Files::MaybeQuotedPath>& files, bool& writeDebugLog, std::vector<Files::MaybeQuotedPath>& archives) { bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files Usages: niftool <nif files, BSA files, or directories> Scan the file or directories for nif errors. Allowed options)"); auto addOption = desc.add_options(); addOption("help,h", "print help message."); addOption("write-debug-log,v", "write debug log for unsupported nif files"); addOption("archives", bpo::value<Files::MaybeQuotedPathContainer>(), "path to archive files to provide files"); addOption("input-file", bpo::value<Files::MaybeQuotedPathContainer>(), "input file"); // Default option if none provided bpo::positional_options_description p; p.add("input-file", -1); bpo::variables_map variables; try { bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(desc).positional(p).run(); bpo::store(valid_opts, variables); bpo::notify(variables); if (variables.count("help")) { std::cout << desc << std::endl; return false; } writeDebugLog = variables.count("write-debug-log") > 0; if (variables.count("input-file")) { files = variables["input-file"].as<Files::MaybeQuotedPathContainer>(); if (const auto it = variables.find("archives"); it != variables.end()) archives = it->second.as<Files::MaybeQuotedPathContainer>(); return true; } } catch (std::exception& e) { std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } std::cout << "No input files or directories specified!" << std::endl; std::cout << desc << std::endl; return false; } int main(int argc, char** argv) { std::vector<Files::MaybeQuotedPath> files; bool writeDebugLog = false; std::vector<Files::MaybeQuotedPath> archives; if (!parseOptions(argc, argv, files, writeDebugLog, archives)) return 1; Nif::Reader::setLoadUnsupportedFiles(true); Nif::Reader::setWriteNifDebugLog(writeDebugLog); std::unique_ptr<VFS::Manager> vfs; if (!archives.empty()) { vfs = std::make_unique<VFS::Manager>(); for (const std::filesystem::path& path : archives) if (auto archive = makeArchive(path)) vfs->addArchive(std::move(archive)); else std::cerr << '"' << path << "\" is unsupported archive" << std::endl; vfs->buildIndex(); } // std::cout << "Reading Files" << std::endl; for (const auto& path : files) { try { if (isNIF(path)) { // std::cout << "Decoding: " << name << std::endl; Nif::NIFFile file(path); Nif::Reader reader(file); if (vfs != nullptr) reader.parse(vfs->get(Files::pathToUnicodeString(path))); else reader.parse(Files::openConstrainedFileStream(path)); } else if (auto archive = makeArchive(path)) { readVFS(std::move(archive), path); } else { std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path) << "\" is not a nif file, bsa file, or directory!" << std::endl; } } catch (std::exception& e) { std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; } } return 0; }