libeuropa/io: Fix PakWriter/PakFile zero-size-bug

This bug was introduced during refactoring (to allow pmdl export support); it causes the writer to not write any file data.

Additionally the writer now sorts by creation time. I'm not sure if greater or less are the correct order.
This commit is contained in:
Lily Tsuru 2025-01-05 20:15:54 -05:00
parent 2c0237933c
commit b74ca42d72
6 changed files with 87 additions and 37 deletions

View File

@ -8,7 +8,7 @@ As per usual for lily, written in C++20.
## Building ## Building
```bash ```bash
$ git clone https://github.com/modeco80/EuropaTools.git $ git clone https://git.crustywindo.ws/GameModding/EuropaTools.git
$ cd EuropaTools $ cd EuropaTools
$ cmake -Bbuild -DCMAKE_BUILD_TYPE=Release $ cmake -Bbuild -DCMAKE_BUILD_TYPE=Release
$ cmake --build build -j $(nproc) $ cmake --build build -j $(nproc)

View File

@ -18,6 +18,10 @@ namespace europa::io {
struct PakReader; struct PakReader;
struct PakWriter; struct PakWriter;
/// Repressents a package file.
/// FIXME: Maybe make this not hold a buffer at some point,
/// or a sumtype which can contain either buffer OR path to os file
/// (which we can then efficiently tee into)
struct PakFile { struct PakFile {
using DataType = std::vector<std::uint8_t>; using DataType = std::vector<std::uint8_t>;
@ -57,6 +61,21 @@ namespace europa::io {
void SetData(DataType&& data) { void SetData(DataType&& data) {
this->data = std::move(data); this->data = std::move(data);
// Update the TOC size.
std::visit([&](auto& entry) {
entry.size = this->data.size();
}, toc);
}
std::uint32_t GetCreationUnixTime() const {
std::uint32_t time{};
std::visit([&](auto& entry) {
time = entry.creationUnixTime;
}, toc);
return time;
} }
std::uint32_t GetOffset() const { std::uint32_t GetOffset() const {

View File

@ -1,7 +1,7 @@
// //
// EuropaTools // EuropaTools
// //
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch> // (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
// //
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// //
@ -16,6 +16,9 @@
namespace europa::io { namespace europa::io {
/// The size of a CD-ROM (ISO 9660) secor.
constexpr auto kCDSectorSize = 0x800;
void PakWriter::SetVersion(structs::PakVersion version) { void PakWriter::SetVersion(structs::PakVersion version) {
// for now. // for now.
this->version = version; this->version = version;
@ -52,7 +55,7 @@ namespace europa::io {
// Sort the flattened array by file size, the biggest first. // Sort the flattened array by file size, the biggest first.
// Doesn't seem to help (neither does name length) // Doesn't seem to help (neither does name length)
std::ranges::sort(sortedFiles, std::greater{}, [](const FlattenedType& elem) { std::ranges::sort(sortedFiles, std::greater{}, [](const FlattenedType& elem) {
return elem.second.GetSize(); return elem.second.GetCreationUnixTime();
}); });
// Leave space for the header // Leave space for the header
@ -64,10 +67,12 @@ namespace europa::io {
os.seekp(6, std::ostream::cur); os.seekp(6, std::ostream::cur);
} }
//os.seekp( // Align first file to sector boundary.
// AlignBy(os.tellp(), 2048), if(sectorAligned)
// std::istream::beg os.seekp(
//); AlignBy(os.tellp(), kCDSectorSize),
std::istream::beg
);
// Write file data // Write file data
for(auto& [filename, file] : sortedFiles) { for(auto& [filename, file] : sortedFiles) {
@ -76,20 +81,21 @@ namespace europa::io {
filename filename
}); });
// Update the offset to where we currently are, since we will be writing the file there
file.Visit([&](auto& tocEntry) { file.Visit([&](auto& tocEntry) {
tocEntry.offset = os.tellp(); tocEntry.offset = os.tellp();
}); });
// FIXME: Should we rely on GetSize() when writing? Honestly, it seems like a bit of a
// mistake that caused a pretty glaring bug.
os.write(reinterpret_cast<const char*>(file.GetData().data()), file.GetSize()); os.write(reinterpret_cast<const char*>(file.GetData().data()), file.GetSize());
//os.seekp( // Align to sector boundary.
// AlignBy(os.tellp(), 2048), if(sectorAligned)
// std::istream::beg os.seekp(
//); AlignBy(os.tellp(), kCDSectorSize),
std::istream::beg
// Flush on file writing );
os.flush();
sink.OnEvent({ sink.OnEvent({
PakProgressReportSink::FileEvent::Type::FileEndWrite, PakProgressReportSink::FileEvent::Type::FileEndWrite,
@ -107,7 +113,7 @@ namespace europa::io {
for(auto& [filename, file] : sortedFiles) { for(auto& [filename, file] : sortedFiles) {
file.FillTOCEntry(); file.FillTOCEntry();
// Write the pstring // Write the filename Pascal string.
os.put(static_cast<char>(filename.length() + 1)); os.put(static_cast<char>(filename.length() + 1));
for(const auto c : filename) for(const auto c : filename)
os.put(c); os.put(c);
@ -116,7 +122,6 @@ namespace europa::io {
file.Visit([&](auto& tocEntry) { file.Visit([&](auto& tocEntry) {
impl::WriteStreamType(os, tocEntry); impl::WriteStreamType(os, tocEntry);
}); });
} }
@ -127,6 +132,7 @@ namespace europa::io {
// Fill out the rest of the header. // Fill out the rest of the header.
pakHeader.fileCount = sortedFiles.size(); pakHeader.fileCount = sortedFiles.size();
pakHeader.tocSize = static_cast<std::uint32_t>(os.tellp()) - (pakHeader.tocOffset - 1); pakHeader.tocSize = static_cast<std::uint32_t>(os.tellp()) - (pakHeader.tocOffset - 1);
pakHeader.creationUnixTime = 132890732;
sink.OnEvent({ sink.OnEvent({

View File

@ -1,11 +1,15 @@
// //
// EuropaTools // EuropaTools
// //
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch> // (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
// //
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// //
// FIXME: Drop libpixel dependency. Instead
// we should just use stbiw directly and provide our own
// simpler/faster utilities for image buffers.
#include <europa/io/YatfReader.hpp> #include <europa/io/YatfReader.hpp>
#include <vector> #include <vector>

View File

@ -6,6 +6,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// //
#include <chrono>
#include <europa/io/PakWriter.hpp> #include <europa/io/PakWriter.hpp>
#include <fstream> #include <fstream>
#include <indicators/cursor_control.hpp> #include <indicators/cursor_control.hpp>
@ -17,7 +18,6 @@
namespace eupak::tasks { namespace eupak::tasks {
struct CreateArchiveReportSink : public europa::io::PakProgressReportSink { struct CreateArchiveReportSink : public europa::io::PakProgressReportSink {
CreateArchiveReportSink(int fileCount = 0) CreateArchiveReportSink(int fileCount = 0)
: europa::io::PakProgressReportSink() { : europa::io::PakProgressReportSink() {
indicators::show_console_cursor(false); indicators::show_console_cursor(false);
@ -123,7 +123,6 @@ namespace eupak::tasks {
if(c == '/') if(c == '/')
c = '\\'; c = '\\';
progress.set_option(indicators::option::PostfixText { relativePathName + " (" + std::to_string(currFile + 1) + '/' + std::to_string(fileCount) + ")" }); progress.set_option(indicators::option::PostfixText { relativePathName + " (" + std::to_string(currFile + 1) + '/' + std::to_string(fileCount) + ")" });
std::ifstream ifs(ent.path(), std::ifstream::binary); std::ifstream ifs(ent.path(), std::ifstream::binary);
@ -142,11 +141,18 @@ namespace eupak::tasks {
ifs.read(reinterpret_cast<char*>(&pakData[0]), pakData.size()); ifs.read(reinterpret_cast<char*>(&pakData[0]), pakData.size());
file.SetData(std::move(pakData));
file.InitAs(args.pakVersion); file.InitAs(args.pakVersion);
//file.GetTOCEntry().creationUnixTime = static_cast<std::uint32_t>(lastModified.time_since_epoch().count()); // Add data
file.SetData(std::move(pakData));
// Setup other stuff like modtime
file.Visit([&](auto& tocEntry) {
// Need to figure out why this is broken and fucked up
//auto casted = std::chrono::clock_cast<std::chrono::system_clock>(lastModified);
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(lastModified);
tocEntry.creationUnixTime = static_cast<std::uint32_t>(seconds.time_since_epoch().count());
});
files.emplace_back(std::make_pair(relativePathName, std::move(file))); files.emplace_back(std::make_pair(relativePathName, std::move(file)));
progress.tick(); progress.tick();

View File

@ -13,10 +13,21 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
// Define this to enable an "interactive" mode where the scramble routine will /// Define this to enable an "interactive" mode where the scramble routine will
// dump out whatever it does to standard out. Undefine this to make it not do so. /// dump out whatever it does to standard out. Undefine this to make it not do so.
#define INTERACTIVE #define INTERACTIVE
/// Define this to use a slower, more academic implementation of the algorithm.
// #define SLOW
#ifdef INTERACTIVE
// We need to use the
#ifndef SLOW
#define SLOW
#endif
#endif
#ifdef SLOW
template <typename... Args> template <typename... Args>
inline std::string StringPrintf(std::string_view format, Args&&... args) { inline std::string StringPrintf(std::string_view format, Args&&... args) {
char buffer[1024]; char buffer[1024];
@ -58,6 +69,10 @@ std::uint32_t swsfScramble(const std::string& code) {
return tally; return tally;
} }
#else
#error FIXME: Import fast/optimized version
#endif
int main(int argc, char** argv) { int main(int argc, char** argv) {
if(argc < 2) { if(argc < 2) {
std::printf("usage: %s [code-string]\n", argv[0]); std::printf("usage: %s [code-string]\n", argv[0]);