You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
4.2 KiB
165 lines
4.2 KiB
#include "mpq/mpq_writer.hpp" |
|
|
|
#include <cstring> |
|
#include <utility> |
|
|
|
#include <mpqfs/mpqfs.h> |
|
|
|
#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
|