Fix the dates of the files inside a .qetz project

miniz is built with MINIZ_NO_TIME so that saving the same project twice
gives the same bytes. Without a clock it writes 0 as each entry's DOS
date: day 0 of month 0, 1980, which is not a date. Unzip tools show it
as 30 Nov 1979, others fail the conversion and show 13 Dec 1901
(reported on PR #1462).

Write 1 Jan 1980 00:00, the first valid DOS date, into the local and
central header of every entry once the archive is built. The bytes stay
reproducible, and miniz stays unmodified. Using the save time instead
was rejected: two saves would no longer give the same file, and miniz
converts through the local time zone, so even a fixed time_t would
differ between machines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-13 21:49:34 +13:00
parent a78e95be2c
commit c240e4e6f7
3 changed files with 72 additions and 3 deletions
+42 -1
View File
@@ -22,6 +22,7 @@
#include <QFile>
#include <QSaveFile>
#include <QSet>
#include <QtEndian>
namespace {
void setError(QString *error, const QString &text)
@@ -44,6 +45,42 @@ bool safeName(const QString &name)
}
return true;
}
//Built with MINIZ_NO_TIME, miniz leaves each entry's DOS date at 0,
//day 0 of month 0, which is no date: unzip shows 30 Nov 1979, other
//tools 13 Dec 1901. Give every entry 1 Jan 1980 00:00 instead, the
//first valid DOS date, so the bytes still do not depend on the time.
constexpr quint16 EntryDosDate = (1 << 5) | 1;
bool setEntryDates(QByteArray *bytes)
{
char *data = bytes->data();
mz_zip_archive zip;
mz_zip_zero_struct(&zip);
if (!mz_zip_reader_init_mem(&zip, data, size_t(bytes->size()), 0)) return false;
bool ok = true;
const mz_uint count = mz_zip_reader_get_num_files(&zip);
for (mz_uint i = 0 ; i < count && ok ; ++i)
{
mz_zip_archive_file_stat stat;
if (!mz_zip_reader_file_stat(&zip, i, &stat)) {
ok = false;
break;
}
//date at offset 12 of the local header, 14 of the central one
const quint64 local = stat.m_local_header_ofs + 12;
const quint64 central = zip.m_central_directory_file_ofs + stat.m_central_dir_ofs + 14;
if (local + 2 > quint64(bytes->size()) || central + 2 > quint64(bytes->size())) {
ok = false;
break;
}
qToLittleEndian<quint16>(EntryDosDate, data + local);
qToLittleEndian<quint16>(EntryDosDate, data + central);
}
mz_zip_reader_end(&zip);
return ok;
}
}
/**
@@ -87,8 +124,12 @@ QByteArray QetZip::toBytes(const QList<Entry> &entries, QString *error)
mz_zip_writer_end(&zip);
return {};
}
const QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
mz_zip_writer_end(&zip); //frees buffer
if (!setEntryDates(&bytes)) {
setError(error, QStringLiteral("cannot date the zip entries"));
return {};
}
return bytes;
}