diff --git a/coalesced.cpp b/coalesced.cpp index 47169b5..6888209 100644 --- a/coalesced.cpp +++ b/coalesced.cpp @@ -48,53 +48,32 @@ bool CoalescedFile::load(const QString& path) m_isCoalesced = true; m_loadedPath = path; - // Parse the coalesced format - const char* ptr = data.constData(); - const char* end = ptr + data.size(); + QDataStream stream(data); + stream.setByteOrder(QDataStream::BigEndian); - // Read version (big-endian) - if (ptr + 4 > end) { - setError("Unexpected end of file reading header"); - return false; - } + quint32 entryCount; + stream >> entryCount; - m_version = qFromBigEndian(reinterpret_cast(ptr)); - ptr += 4; + entryCount /= 2; - // Read entries until EOF - while (ptr + 4 <= end) { - quint32 filenameLen = qFromBigEndian(reinterpret_cast(ptr)); - ptr += 4; - - // Sanity check filename length - 0 or very large means we've hit end of entries + for (uint i = 0; i < entryCount; i++) + { + quint32 filenameLen; + stream >> filenameLen; if (filenameLen == 0 || filenameLen > 1024) { break; } - // Read filename - if (ptr + filenameLen > end) { - break; - } + QByteArray filename(filenameLen - 1, Qt::Uninitialized); + stream.readRawData(filename.data(), filenameLen - 1); + stream.skipRawData(1); - // Filename includes null terminator - QString filename = QString::fromLatin1(ptr, filenameLen > 0 ? filenameLen - 1 : 0); - ptr += filenameLen; + quint32 contentLen; + stream >> contentLen; - // Read content length - if (ptr + 4 > end) { - break; - } - - quint32 contentLen = qFromBigEndian(reinterpret_cast(ptr)); - ptr += 4; - - // Read content - if (ptr + contentLen > end) { - break; - } - - QByteArray content(ptr, contentLen); - ptr += contentLen; + QByteArray content(contentLen - 1, Qt::Uninitialized); + stream.readRawData(content.data(), contentLen - 1); + stream.skipRawData(1); IniEntry entry; entry.filename = filename; @@ -116,10 +95,11 @@ bool CoalescedFile::save(const QString& path) const stream.setByteOrder(QDataStream::BigEndian); // Write version - stream << m_version; + stream << (quint32)m_entries.size() * 2; // Write each entry - for (const IniEntry& entry : m_entries) { + for (uint i = 0; i < m_entries.size(); i++) { + const IniEntry& entry = m_entries.at(i); // Convert filename to Latin1 with null terminator QByteArray filenameBytes = entry.filename.toLatin1(); filenameBytes.append('\0'); @@ -130,11 +110,14 @@ bool CoalescedFile::save(const QString& path) const // Write filename stream.writeRawData(filenameBytes.constData(), filenameBytes.size()); + QByteArray contentBytes = entry.content; + contentBytes.append('\0'); + // Write content length - stream << static_cast(entry.content.size()); + stream << static_cast(contentBytes.size()); // Write content - stream.writeRawData(entry.content.constData(), entry.content.size()); + stream.writeRawData(contentBytes.constData(), contentBytes.size()); } file.close(); @@ -237,10 +220,38 @@ bool CoalescedFile::packDirectory(const QString& dirPath, const QString& basePat filters << "*.ini" << "*.int" << "*.jpn" << "*.deu" << "*.esn" << "*.fra" << "*.ita" << "*.kor" << "*.pol" << "*.rus" << "*.cze" << "*.hun" << "*.esm" << "*.ptb"; - QDirIterator it(dirPath, filters, QDir::Files, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString filePath = it.next(); + int entryCount = 0; + QDirIterator entryIt(dirPath, filters, QDir::Files, QDirIterator::Subdirectories); + while (entryIt.hasNext()) + { + entryIt.next(); + entryCount++; + } + + // Read temp file for entry records + QFile tempFile(dirPath + "/temp"); + if (!tempFile.open(QIODevice::ReadOnly)) + { + setError(QString("Failed to open temp file: %1").arg(tempFile.errorString())); + return 1; + } + + // Parse filenames + QStringList entryStrings; + for (int i = 0; i < entryCount; i++) + { + QByteArray entryStr(64, Qt::Uninitialized); + qint64 lineLen = tempFile.readLine(entryStr.data(), 64); + + entryStrings.push_back(entryStr.mid(0, lineLen - 1)); + } + tempFile.close(); + + for (int i = 0; i < entryStrings.size(); i++) + { + QString entryString = entryStrings.at(i); + const QString& filePath = dirPath + entryString.replace("..", ""); QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { diff --git a/main.cpp b/main.cpp index d2149fe..34fab46 100644 --- a/main.cpp +++ b/main.cpp @@ -120,28 +120,34 @@ int cmdInfo(const QStringList& args) int cmdUnpack(const QStringList& args) { - if (args.isEmpty()) { + if (args.isEmpty()) + { printError(QString("Usage: %1 unpack [output_dir]").arg(QCoreApplication::applicationName())); return 1; } CoalescedFile coalesced; - if (!coalesced.load(args[0])) { + if (!coalesced.load(args[0])) + { printError(coalesced.lastError()); return 1; } QString outputDir; - if (args.size() > 1) { + if (args.size() > 1) + { outputDir = args[1]; - } else { + } + else + { // Default: use filename with _ instead of . (e.g., coalesced.ini -> coalesced_ini) QFileInfo fi(args[0]); outputDir = fi.completeBaseName() + "_" + fi.suffix(); } QDir dir(outputDir); - if (!dir.exists() && !dir.mkpath(".")) { + if (!dir.exists() && !dir.mkpath(".")) + { printError(QString("Cannot create output directory: %1").arg(outputDir)); return 1; } @@ -149,11 +155,28 @@ int cmdUnpack(const QStringList& args) int extracted = 0; qint64 totalBytes = 0; - for (const IniEntry& entry : coalesced.entries()) { + QFile tempFile(outputDir + "/temp"); + if (!tempFile.open(QIODevice::WriteOnly)) + { + printError(QString("Failed to open temp file: %1").arg(tempFile.errorString())); + return 1; + } + + for (int i = 0; i < coalesced.entries().size(); i++) + { + QString entryStr(coalesced.entries().at(i).filename + "\n"); + tempFile.write(entryStr.toUtf8()); + } + tempFile.close(); + + for (int i = 0; i < coalesced.entries().size(); i++) + { + const IniEntry& entry = coalesced.entries().at(i); QString localPath = entry.filename; // Remove leading ".." components for safety - while (localPath.startsWith("..\\") || localPath.startsWith("../")) { + while (localPath.startsWith("..\\") || localPath.startsWith("../")) + { localPath = localPath.mid(3); } localPath.replace('\\', '/'); @@ -161,13 +184,15 @@ int cmdUnpack(const QStringList& args) QString fullPath = dir.filePath(localPath); QFileInfo fi(fullPath); - if (!fi.dir().exists() && !QDir().mkpath(fi.path())) { + if (!fi.dir().exists() && !QDir().mkpath(fi.path())) + { printError(QString("Cannot create directory: %1").arg(fi.path())); continue; } QFile file(fullPath); - if (!file.open(QIODevice::WriteOnly)) { + if (!file.open(QIODevice::WriteOnly)) + { printError(QString("Cannot write file: %1").arg(fullPath)); continue; }