#include "mpq/mpq_writer.hpp" #include #include #include #include "mpq/mpq_common.hpp" #include "utils/file_util.h" #include "utils/log.hpp" namespace devilution { MpqWriter::MpqWriter(const char *path, bool carryForward) : path_(path) { const std::string dir = std::string(Dirname(path)); if (!dir.empty()) { RecursivelyCreateDir(dir.c_str()); } LogVerbose("Opening {}", path); // If the file already exists and we need to preserve its contents, // rename it to a temp path so we can read from it after the writer // truncates the original path. std::string tmpPath; mpqfs_archive_t *oldArchive = nullptr; if (carryForward && FileExists(path)) { tmpPath = std::string(path) + ".tmp"; // Remove any stale temp file, then rename old → tmp. ::devilution::RemoveFile(tmpPath.c_str()); ::devilution::RenameFile(path, tmpPath.c_str()); oldArchive = mpqfs_open(tmpPath.c_str()); // If it fails to open (e.g. corrupt), we proceed without // carry-forward — the file will be recreated from scratch. } writer_ = mpqfs_writer_create(path, MpqWriterHashTableSize); if (writer_ == nullptr) { LogError("Failed to create MPQ archive: {}", mpqfs_last_error()); if (oldArchive != nullptr) mpqfs_close(oldArchive); if (!tmpPath.empty()) ::devilution::RemoveFile(tmpPath.c_str()); return; } if (oldArchive != nullptr) { if (!mpqfs_writer_carry_forward_all(writer_, oldArchive)) { LogError("Failed to carry forward files from existing archive: {}", mpqfs_last_error()); } mpqfs_close(oldArchive); } if (!tmpPath.empty()) ::devilution::RemoveFile(tmpPath.c_str()); } MpqWriter::MpqWriter(MpqWriter &&other) noexcept : path_(std::move(other.path_)) , writer_(other.writer_) { other.writer_ = nullptr; } MpqWriter &MpqWriter::operator=(MpqWriter &&other) noexcept { if (this != &other) { if (writer_ != nullptr) mpqfs_writer_discard(writer_); path_ = std::move(other.path_); writer_ = other.writer_; other.writer_ = nullptr; } return *this; } MpqWriter::~MpqWriter() { if (writer_ == nullptr) return; LogVerbose("Closing {}", path_); if (!mpqfs_writer_close(writer_)) { LogError("Failed to close MPQ archive: {}", mpqfs_last_error()); } } bool MpqWriter::HasFile(std::string_view name) const { if (writer_ == nullptr) return false; char buf[MaxMpqPathSize]; if (name.size() >= sizeof(buf)) return false; std::memcpy(buf, name.data(), name.size()); buf[name.size()] = '\0'; return mpqfs_writer_has_file(writer_, buf); } void MpqWriter::RemoveHashEntry(std::string_view filename) { if (writer_ == nullptr) return; char buf[MaxMpqPathSize]; if (filename.size() >= sizeof(buf)) return; std::memcpy(buf, filename.data(), filename.size()); buf[filename.size()] = '\0'; mpqfs_writer_remove_file(writer_, buf); } void MpqWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) { char pszFileName[MaxMpqPathSize]; for (uint8_t i = 0; fnGetName(i, pszFileName); i++) { RemoveHashEntry(pszFileName); } } bool MpqWriter::WriteFile(std::string_view filename, const std::byte *data, size_t size) { if (writer_ == nullptr) return false; char buf[MaxMpqPathSize]; if (filename.size() >= sizeof(buf)) return false; std::memcpy(buf, filename.data(), filename.size()); buf[filename.size()] = '\0'; // Remove any previous entry with this name so that // only the latest write is included in the archive. mpqfs_writer_remove_file(writer_, buf); if (!mpqfs_writer_add_file(writer_, buf, data, size)) { LogError("Failed to write file '{}' to MPQ: {}", filename, mpqfs_last_error()); return false; } return true; } void MpqWriter::RenameFile(std::string_view name, std::string_view newName) { if (writer_ == nullptr) return; char oldBuf[MaxMpqPathSize]; char newBuf[MaxMpqPathSize]; if (name.size() >= sizeof(oldBuf) || newName.size() >= sizeof(newBuf)) return; std::memcpy(oldBuf, name.data(), name.size()); oldBuf[name.size()] = '\0'; std::memcpy(newBuf, newName.data(), newName.size()); newBuf[newName.size()] = '\0'; mpqfs_writer_rename_file(writer_, oldBuf, newBuf); } } // namespace devilution