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);
@ -32,17 +32,17 @@ namespace eupak::tasks {
using enum PakEvent::Type; using enum PakEvent::Type;
switch(event.type) { switch(event.type) {
case WritingHeader: case WritingHeader:
progress.set_option(indicators::option::PostfixText {"Writing header"}); progress.set_option(indicators::option::PostfixText { "Writing header" });
progress.print_progress(); progress.print_progress();
break; break;
case FillInHeader: case FillInHeader:
progress.set_option(indicators::option::PostfixText {"Filling in header"}); progress.set_option(indicators::option::PostfixText { "Filling in header" });
progress.print_progress(); progress.print_progress();
break; break;
case WritingToc: case WritingToc:
progress.set_option(indicators::option::PostfixText {"Writing TOC"}); progress.set_option(indicators::option::PostfixText { "Writing TOC" });
progress.print_progress(); progress.print_progress();
break; break;
} }
@ -52,12 +52,12 @@ namespace eupak::tasks {
using enum FileEvent::Type; using enum FileEvent::Type;
switch(event.type) { switch(event.type) {
case FileBeginWrite: case FileBeginWrite:
progress.set_option(indicators::option::PostfixText {"Writing " + event.filename}); progress.set_option(indicators::option::PostfixText { "Writing " + event.filename });
progress.print_progress(); progress.print_progress();
break; break;
case FileEndWrite: case FileEndWrite:
progress.set_option(indicators::option::PostfixText {"Written " + event.filename}); progress.set_option(indicators::option::PostfixText { "Written " + event.filename });
progress.tick(); progress.tick();
break; break;
} }
@ -123,8 +123,7 @@ 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,11 +13,22 @@
#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
template<typename... Args> /// 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>
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];
auto len = std::snprintf(&buffer[0], sizeof(buffer) - 1, format.data(), static_cast<Args&&>(args)...); auto len = std::snprintf(&buffer[0], sizeof(buffer) - 1, format.data(), static_cast<Args&&>(args)...);
@ -36,13 +47,13 @@ std::uint32_t swsfScramble(const std::string& code) {
for(auto& c : copy) for(auto& c : copy)
c = tolower(c); c = tolower(c);
#ifdef INTERACTIVE #ifdef INTERACTIVE
std::printf("working with string \"%s\"\n", copy.c_str()); std::printf("working with string \"%s\"\n", copy.c_str());
#endif #endif
// Now actually do the scramble. The algorithm is pretty simple. // Now actually do the scramble. The algorithm is pretty simple.
for(auto* p = copy.data(); *p; ++p) { for(auto* p = copy.data(); *p; ++p) {
#ifdef INTERACTIVE #ifdef INTERACTIVE
std::uint32_t clone = *p; std::uint32_t clone = *p;
std::printf("load character '%c' -> 0x%08x (%d)\n", *p, clone, clone); std::printf("load character '%c' -> 0x%08x (%d)\n", *p, clone, clone);
@ -50,14 +61,18 @@ std::uint32_t swsfScramble(const std::string& code) {
std::printf("add (0x%08x (%d) * 5) (%d) -> 0x%08x (%d)\n", tally, tally, tally * 5, clone, clone); std::printf("add (0x%08x (%d) * 5) (%d) -> 0x%08x (%d)\n", tally, tally, tally * 5, clone, clone);
tally = clone; tally = clone;
#else #else
tally = *p + (tally * 5); tally = *p + (tally * 5);
#endif #endif
} }
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]);