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

#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