#include "../utility/file.hpp" #include "../utility/log.hpp" #include "../utility/path.hpp" #include "../utility/platform.hpp" #ifdef DEVKITPRO #include "../utility/thread_local.hpp" #endif #include #include #include #if defined(QT_GUI) && defined(EMBED_DATA) #include #include #endif namespace randomizer::utility::file { bool isRoot(const fspath& fsPath) { static const std::regex rootFilesystem(R"(^fs:\/vol\/[^\/:]+\/?$)"); const std::string path = fsPath.string(); if (path.size() >= 2 && path.ends_with(":")) return true; if (path.size() >= 3 && path.ends_with(":/")) return true; if (std::regex_match(path, rootFilesystem)) return true; return false; }; #ifdef DEVKITPRO static constexpr int FILE_BUF_SIZE = 25 * 1024 * 1024; class AlignedBufferWrapper { private: alignas(0x40) char buffer[FILE_BUF_SIZE]; public: char* getBuffer() { return buffer; } }; static ThreadLocal buf; #endif bool copy_file(const fspath& from, const fspath& to) { randomizer::utility::platform::Log("Copying " + Utility::toUtf8String(to)); #ifdef DEVKITPRO // use a buffer to speed up file copying std::ifstream src(from, std::ios::binary); std::ofstream dst(to, std::ios::binary); if (!src.is_open()) { ErrorLog::getInstance().log("Failed to open " + from.string()); return false; } if (!dst.is_open()) { ErrorLog::getInstance().log("Failed to open " + to.string()); return false; } while (src) { src.read(buf.get().getBuffer(), FILE_BUF_SIZE); dst.write(buf.get().getBuffer(), src.gcount()); } return true; #else // GNU on windows currently has a bug where you can't copy over a file that already exists // even if you pass std::filesystem::copy_options::overwrite_existing. So delete the copy location // file in this case #if defined(WIN32) && defined(__GNUG__) std::filesystem::remove(to); #endif return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); #endif } bool copy(const fspath& from, const fspath& to) { #ifdef DEVKITPRO // based on https://github.com/emiyl/dumpling/blob/12935ede46e9720fdec915cdb430d10eb7df54a7/source/app/dumping.cpp#L208 DIR* dirHandle; if ((dirHandle = opendir(from.string().c_str())) == nullptr) { ErrorLog::getInstance().log("Couldn't open directory to copy files from: " + to.string()); return false; } randomizer::utility::file::create_directories(to); // Loop over directory contents struct dirent* dirEntry; while ((dirEntry = readdir(dirHandle)) != nullptr) { const std::string entrySrcPath = from / dirEntry->d_name; const std::string entryDstPath = to / dirEntry->d_name; // Use lstat since readdir returns DT_REG for symlinks struct stat fileStat; if (lstat(entrySrcPath.c_str(), &fileStat) != 0) { ErrorLog::getInstance().log("Couldn't check what type this file/folder was: " + entrySrcPath); return false; } if (S_ISLNK(fileStat.st_mode)) { continue; } else if (S_ISREG(fileStat.st_mode)) { // Copy file if (!copy_file(entrySrcPath, entryDstPath)) { ErrorLog::getInstance().log("Failed to copy file: " + entrySrcPath); closedir(dirHandle); return false; } } else if (S_ISDIR(fileStat.st_mode)) { // Ignore root and parent folder entries if (std::strncmp(dirEntry->d_name, ".", 1) == 0 || std::strncmp(dirEntry->d_name, "..", 2) == 0) continue; // Copy all the files in this subdirectory if (!copy(entrySrcPath, entryDstPath)) { ErrorLog::getInstance().log("Failed to copy dir: " + entrySrcPath); closedir(dirHandle); return false; } } } closedir(dirHandle); #else std::filesystem::copy(from, to, std::filesystem::copy_options::recursive); #endif return true; } // Short function for getting the string data from a file int GetContents(const fspath& filename, std::string& fileContents, bool resourceFile /*= false*/) { if (resourceFile) { // If this is a resource file and the data has been embedded, then load it from // the embedded resources file #if defined(QT_GUI) && defined(EMBED_DATA) QResource file(Utility::toQString(filename)); if (!file.isValid()) { return 1; } QByteArray data = file.uncompressedData(); if (data.isNull()) { return 1; } fileContents = data.toStdString(); return 0; #endif } // Otherwise load it normally auto ss = std::stringstream {}; if (const auto err = GetContents(filename, ss); err != 0) return err; fileContents = ss.str(); return 0; } // Short function for getting the string data from a file int GetContents(const fspath& filename, std::stringstream& fileContents) { // Otherwise load it normally std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { LOG_TO_ERROR("Unable to open file \"" + Utility::toUtf8String(filename) + "\""); return 1; } #ifdef DEVKITPRO while (file) { file.read(buf.get().getBuffer(), FILE_BUF_SIZE); fileContents.write(buf.get().getBuffer(), file.gcount()); } #else fileContents << file.rdbuf(); #endif return 0; } void Verify(const fspath& filename) { std::ifstream file(filename); if (!file.is_open()) { throw std::runtime_error("Could not open " + Utility::toUtf8String(filename)); } file.close(); } } // namespace randomizer::utility::file