Initial support for Ver3/.PMDL archives
................. fuck This really needs to be cleaned up before I'm willing to call it "good" but ultimately the API changes here needed to be done anyhow
This commit is contained in:
parent
5272175a21
commit
2c0237933c
|
@ -1,5 +1,4 @@
|
||||||
/.idea
|
/.cache
|
||||||
cmake-build-*
|
|
||||||
build/
|
build/
|
||||||
|
|
||||||
# swap
|
# swap
|
||||||
|
|
|
@ -21,28 +21,81 @@ namespace europa::io {
|
||||||
struct PakFile {
|
struct PakFile {
|
||||||
using DataType = std::vector<std::uint8_t>;
|
using DataType = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void InitAs(const T& value) {
|
||||||
|
toc = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitAs(structs::PakVersion version) {
|
||||||
|
switch(version) {
|
||||||
|
case structs::PakVersion::Ver3:
|
||||||
|
toc = structs::PakHeader_V3::TocEntry{};
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver4:
|
||||||
|
toc = structs::PakHeader_V4::TocEntry{};
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver5:
|
||||||
|
toc = structs::PakHeader_V5::TocEntry{};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the file data.
|
* Get the file data.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] const DataType& GetData() const;
|
[[nodiscard]] const DataType& GetData() const {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the TOC entry responsible.
|
* Get the TOC entry responsible.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] const structs::PakTocEntry& GetTOCEntry() const;
|
template<class T>
|
||||||
|
[[nodiscard]] const T& GetTOCEntry() const {
|
||||||
|
return std::get<T>(toc);
|
||||||
|
}
|
||||||
|
|
||||||
void SetData(DataType&& data);
|
void SetData(DataType&& data) {
|
||||||
|
this->data = std::move(data);
|
||||||
|
}
|
||||||
|
|
||||||
structs::PakTocEntry& GetTOCEntry();
|
std::uint32_t GetOffset() const {
|
||||||
|
std::uint32_t size{};
|
||||||
|
|
||||||
void FillTOCEntry();
|
std::visit([&](auto& entry) {
|
||||||
|
size = entry.offset;
|
||||||
|
}, toc);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t GetSize() const {
|
||||||
|
std::uint32_t size{};
|
||||||
|
|
||||||
|
std::visit([&](auto& entry) {
|
||||||
|
size = entry.size;
|
||||||
|
}, toc);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillTOCEntry() {
|
||||||
|
std::visit([&](auto& entry) {
|
||||||
|
entry.size = static_cast<std::uint32_t>(data.size());
|
||||||
|
}, toc);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Cb>
|
||||||
|
void Visit(const Cb& cb) {
|
||||||
|
std::visit(cb, toc);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend PakReader;
|
friend PakReader;
|
||||||
friend PakWriter;
|
friend PakWriter;
|
||||||
|
|
||||||
std::vector<std::uint8_t> data;
|
std::vector<std::uint8_t> data;
|
||||||
structs::PakTocEntry tocData;
|
structs::PakTocEntryVariant toc;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace europa::io
|
} // namespace europa::io
|
||||||
|
|
|
@ -15,11 +15,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace europa::io {
|
namespace europa::io {
|
||||||
|
|
||||||
struct PakReader {
|
struct PakReader {
|
||||||
using MapType = std::unordered_map<std::string, PakFile>;
|
using MapType = std::unordered_map<std::string, PakFile>;
|
||||||
|
|
||||||
|
|
||||||
explicit PakReader(std::istream& is);
|
explicit PakReader(std::istream& is);
|
||||||
|
|
||||||
void ReadData();
|
void ReadData();
|
||||||
|
@ -39,13 +42,17 @@ namespace europa::io {
|
||||||
const MapType& GetFiles() const;
|
const MapType& GetFiles() const;
|
||||||
|
|
||||||
// implement in cpp later, lazy and just wanna get this out :vvv
|
// implement in cpp later, lazy and just wanna get this out :vvv
|
||||||
const structs::PakHeader& GetHeader() const { return header; }
|
const structs::PakHeaderVariant& GetHeader() const { return header; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template<class T>
|
||||||
|
void ReadData_Impl();
|
||||||
|
|
||||||
std::istream& stream;
|
std::istream& stream;
|
||||||
bool invalid { false };
|
bool invalid { false };
|
||||||
|
|
||||||
structs::PakHeader header {};
|
structs::PakVersion version;
|
||||||
|
structs::PakHeaderVariant header {};
|
||||||
|
|
||||||
MapType files;
|
MapType files;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "europa/structs/Pak.hpp"
|
||||||
|
|
||||||
namespace europa::io {
|
namespace europa::io {
|
||||||
|
|
||||||
|
@ -23,9 +24,11 @@ namespace europa::io {
|
||||||
struct PakWriter {
|
struct PakWriter {
|
||||||
using FlattenedType = std::pair<std::string, PakFile>;
|
using FlattenedType = std::pair<std::string, PakFile>;
|
||||||
|
|
||||||
void Init(structs::PakHeader::Version version);
|
//void Init(structs::PakHeader::Version version);
|
||||||
|
|
||||||
const structs::PakHeader& GetHeader() const { return pakHeader; }
|
//const HeaderType& GetHeader() const { return pakHeader; }
|
||||||
|
|
||||||
|
void SetVersion(structs::PakVersion version);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the resulting archive to the given output stream.
|
* Write the resulting archive to the given output stream.
|
||||||
|
@ -33,7 +36,12 @@ namespace europa::io {
|
||||||
void Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink);
|
void Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
structs::PakHeader pakHeader {};
|
|
||||||
|
template<class T>
|
||||||
|
void WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, bool sectorAligned = true);
|
||||||
|
|
||||||
|
structs::PakVersion version{};
|
||||||
|
//HeaderType pakHeader {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace europa::io
|
} // namespace europa::io
|
||||||
|
|
|
@ -12,17 +12,23 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <europa/structs/ImHexAdapter.hpp>
|
#include <europa/structs/ImHexAdapter.hpp>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace europa::structs {
|
namespace europa::structs {
|
||||||
|
|
||||||
struct [[gnu::packed]] PakHeader {
|
constexpr static const char VALID_MAGIC[16] = "Europa Packfile";
|
||||||
constexpr static const char VALID_MAGIC[16] = "Europa Packfile";
|
|
||||||
|
|
||||||
enum class Version : u16 {
|
enum class PakVersion : u16 {
|
||||||
Ver4 = 0x4,
|
Invalid = 0xffff,
|
||||||
Ver5 = 0x5
|
Ver3 = 0x3, ///< Typically used for PMDL files
|
||||||
};
|
Ver4 = 0x4,
|
||||||
|
Ver5 = 0x5
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] PakHeader_Common {
|
||||||
char magic[16]; // "Europa Packfile\0"
|
char magic[16]; // "Europa Packfile\0"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +36,91 @@ namespace europa::structs {
|
||||||
*/
|
*/
|
||||||
u16 headerSize;
|
u16 headerSize;
|
||||||
|
|
||||||
Version version;
|
PakVersion version;
|
||||||
|
|
||||||
|
bool Valid() const {
|
||||||
|
return !std::strcmp(magic, VALID_MAGIC);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Impl, PakVersion Version>
|
||||||
|
struct [[gnu::packed]] PakHeader_Impl : PakHeader_Common {
|
||||||
|
constexpr static auto VERSION = Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the real header size (including the magic).
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr std::size_t RealHeaderSize() const {
|
||||||
|
return sizeof(magic) + static_cast<std::size_t>(headerSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static u16 HeaderSize() {
|
||||||
|
return sizeof(Impl) - (sizeof(VALID_MAGIC) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PakHeader_Impl() {
|
||||||
|
// clear any junk
|
||||||
|
memset(this, 0, sizeof(PakHeader_Impl));
|
||||||
|
|
||||||
|
version = Version;
|
||||||
|
|
||||||
|
// Copy important things & set proper header size.
|
||||||
|
std::memcpy(&magic[0], &VALID_MAGIC[0], sizeof(VALID_MAGIC));
|
||||||
|
headerSize = HeaderSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit PakHeader_Impl(const PakHeader_Common& header) {
|
||||||
|
memcpy(&magic[0], &header.magic[0], sizeof(header.magic));
|
||||||
|
version = header.version;
|
||||||
|
headerSize = header.headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool Valid() const noexcept {
|
||||||
|
// Magic must match.
|
||||||
|
if(!reinterpret_cast<const PakHeader_Common*>(this)->Valid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check header size.
|
||||||
|
if(headerSize != HeaderSize() && headerSize != HeaderSize() + 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return version == Version;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] PakHeader_V3 : public PakHeader_Impl<PakHeader_V3, PakVersion::Ver3> {
|
||||||
|
using PakHeader_Impl<PakHeader_V3, PakVersion::Ver3>::VERSION;
|
||||||
|
using PakHeader_Impl<PakHeader_V3, PakVersion::Ver3>::PakHeader_Impl;
|
||||||
|
using PakHeader_Impl<PakHeader_V3, PakVersion::Ver3>::Valid;
|
||||||
|
|
||||||
|
struct [[gnu::packed]] TocEntry {
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
u32 creationUnixTime; // junk on these v3 files
|
||||||
|
u16 junk;
|
||||||
|
};
|
||||||
|
|
||||||
|
u32 tocOffset;
|
||||||
|
|
||||||
|
u32 tocSize;
|
||||||
|
|
||||||
|
u32 fileCount;
|
||||||
|
|
||||||
|
u32 creationUnixTime;
|
||||||
|
|
||||||
|
// Zeroes.
|
||||||
|
u32 reservedPad{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] PakHeader_V4 : public PakHeader_Impl<PakHeader_V4, PakVersion::Ver4> {
|
||||||
|
using PakHeader_Impl<PakHeader_V4, PakVersion::Ver4>::PakHeader_Impl;
|
||||||
|
|
||||||
|
struct [[gnu::packed]] TocEntry {
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
u32 creationUnixTime;
|
||||||
|
};
|
||||||
|
|
||||||
u8 pad;
|
u8 pad;
|
||||||
|
|
||||||
u32 tocOffset;
|
u32 tocOffset;
|
||||||
|
@ -43,66 +133,53 @@ namespace europa::structs {
|
||||||
|
|
||||||
// Zeroes.
|
// Zeroes.
|
||||||
u32 reservedPad;
|
u32 reservedPad;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the real header size (including the magic).
|
|
||||||
*/
|
|
||||||
[[nodiscard]] constexpr std::size_t RealHeaderSize() const {
|
|
||||||
return sizeof(magic) + static_cast<std::size_t>(headerSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize this header (used when writing).
|
|
||||||
*/
|
|
||||||
void Init(Version ver) noexcept {
|
|
||||||
// clear any junk
|
|
||||||
memset(this, 0, sizeof(PakHeader));
|
|
||||||
|
|
||||||
// Copy important things.
|
|
||||||
std::memcpy(&magic[0], &VALID_MAGIC[0], sizeof(VALID_MAGIC));
|
|
||||||
|
|
||||||
// Set proper header size.
|
|
||||||
headerSize = sizeof(PakHeader) - (sizeof(PakHeader::VALID_MAGIC) - 1);
|
|
||||||
|
|
||||||
// Set archive version
|
|
||||||
version = ver;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool Valid() const noexcept {
|
|
||||||
// Magic must match.
|
|
||||||
if(std::strcmp(magic, VALID_MAGIC) != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check header size.
|
|
||||||
if(headerSize != sizeof(PakHeader) - (sizeof(PakHeader::VALID_MAGIC) - 1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
using enum Version;
|
|
||||||
|
|
||||||
// Version must match ones we support,
|
|
||||||
// otherwise it's invalid.
|
|
||||||
switch(version) {
|
|
||||||
case Ver4:
|
|
||||||
case Ver5:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A Toc entry (without string. Needs to be read in separately)
|
struct [[gnu::packed]] PakHeader_V5 : public PakHeader_Impl<PakHeader_V5, PakVersion::Ver5> {
|
||||||
struct [[gnu::packed]] PakTocEntry {
|
using PakHeader_Impl<PakHeader_V5, PakVersion::Ver5>::PakHeader_Impl;
|
||||||
u32 offset;
|
|
||||||
u32 size;
|
struct [[gnu::packed]] TocEntry {
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
u32 creationUnixTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
u8 pad;
|
||||||
|
|
||||||
|
u32 tocOffset;
|
||||||
|
|
||||||
|
u32 tocSize;
|
||||||
|
|
||||||
|
u32 fileCount;
|
||||||
|
|
||||||
u32 creationUnixTime;
|
u32 creationUnixTime;
|
||||||
|
|
||||||
|
// Zeroes.
|
||||||
|
u32 reservedPad;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PakHeaderVariant = std::variant<
|
||||||
|
structs::PakHeader_V3,
|
||||||
|
structs::PakHeader_V4,
|
||||||
|
structs::PakHeader_V5>;
|
||||||
|
|
||||||
static_assert(sizeof(PakHeader) == 0x29, "PakHeader wrong size!!");
|
using PakTocEntryVariant = std::variant<
|
||||||
static_assert(sizeof(PakHeader) - (sizeof(PakHeader::VALID_MAGIC) - 1) == 0x1a, "PakHeader::headerSize will be invalid when writing archives.");
|
structs::PakHeader_V3::TocEntry,
|
||||||
static_assert(sizeof(PakTocEntry) == 0xc, "PakTocEntry wrong size!");
|
structs::PakHeader_V4::TocEntry,
|
||||||
|
structs::PakHeader_V5::TocEntry>;
|
||||||
|
|
||||||
|
static_assert(sizeof(PakHeader_V3) == 0x28, "PakHeader_V3 wrong size");
|
||||||
|
// TODO: their format really seems to be wrong, 0x19 is proper, but some v3 archives have 0x1a header size
|
||||||
|
// ??? very weird
|
||||||
|
//static_assert(sizeof(PakHeader_V3) - (sizeof(VALID_MAGIC) - 1) == 0x1a, "PakHeader_V3::headerSize will be invalid when writing archives.");
|
||||||
|
static_assert(sizeof(PakHeader_V4) == 0x29, "PakHeader_V4 wrong size!!");
|
||||||
|
static_assert(sizeof(PakHeader_V4) - (sizeof(VALID_MAGIC) - 1) == 0x1a, "PakHeader_V4::headerSize will be invalid when writing archives.");
|
||||||
|
static_assert(sizeof(PakHeader_V5) == 0x29, "PakHeader_V5 wrong size!!");
|
||||||
|
static_assert(sizeof(PakHeader_V5) - (sizeof(VALID_MAGIC) - 1) == 0x1a, "PakHeader_V5::headerSize will be invalid when writing archives.");
|
||||||
|
|
||||||
|
static_assert(sizeof(PakHeader_V3::TocEntry) == 0xe, "V3 TocEntry wrong size!");
|
||||||
|
static_assert(sizeof(PakHeader_V4::TocEntry) == 0xc, "V4 PakTocEntry wrong size!");
|
||||||
|
static_assert(sizeof(PakHeader_V5::TocEntry) == 0xc, "V5 PakTocEntry wrong size!");
|
||||||
|
|
||||||
} // namespace europa::structs
|
} // namespace europa::structs
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace europa::structs {
|
||||||
*/
|
*/
|
||||||
constexpr static u32 TextureFlag_UsesAlpha = 0x1000000;
|
constexpr static u32 TextureFlag_UsesAlpha = 0x1000000;
|
||||||
|
|
||||||
constexpr static auto ValidMagic = util::FourCC<"YATF", std::endian::big>();
|
constexpr static auto ValidMagic = util::FourCC<"YATF", std::endian::little>();
|
||||||
|
|
||||||
u32 magic;
|
u32 magic;
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ add_library(europa
|
||||||
io/StreamUtils.cpp
|
io/StreamUtils.cpp
|
||||||
|
|
||||||
# Pak IO
|
# Pak IO
|
||||||
io/PakFile.cpp
|
|
||||||
io/PakReader.cpp
|
io/PakReader.cpp
|
||||||
io/PakWriter.cpp
|
io/PakWriter.cpp
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// EuropaTools
|
|
||||||
//
|
|
||||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <europa/io/PakFile.hpp>
|
|
||||||
|
|
||||||
namespace europa::io {
|
|
||||||
|
|
||||||
const PakFile::DataType& PakFile::GetData() const {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const structs::PakTocEntry& PakFile::GetTOCEntry() const {
|
|
||||||
return tocData;
|
|
||||||
}
|
|
||||||
|
|
||||||
structs::PakTocEntry& PakFile::GetTOCEntry() {
|
|
||||||
return tocData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PakFile::SetData(PakFile::DataType&& newData) {
|
|
||||||
data = std::move(newData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PakFile::FillTOCEntry() {
|
|
||||||
tocData.size = static_cast<std::uint32_t>(data.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace europa::io
|
|
|
@ -14,27 +14,83 @@
|
||||||
|
|
||||||
namespace europa::io {
|
namespace europa::io {
|
||||||
|
|
||||||
|
/*
|
||||||
|
inline std::optional<PakHeader> GetPakHeader(const PakHeader_Common& common_header) {
|
||||||
|
switch(common_header.version) {
|
||||||
|
case PakVersion::Ver3:
|
||||||
|
return PakHeader_V3(common_header);
|
||||||
|
|
||||||
|
case PakVersion::Ver4:
|
||||||
|
return PakHeader_V4(common_header);
|
||||||
|
|
||||||
|
case PakVersion::Ver5:
|
||||||
|
return PakHeader_V5(common_header);
|
||||||
|
|
||||||
|
case PakVersion::Invalid:
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PakReader::PakReader(std::istream& is)
|
PakReader::PakReader(std::istream& is)
|
||||||
: stream(is) {
|
: stream(is) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PakReader::ReadData() {
|
template<class T>
|
||||||
header = impl::ReadStreamType<structs::PakHeader>(stream);
|
void PakReader::ReadData_Impl() {
|
||||||
|
auto header_type = impl::ReadStreamType<T>(stream);
|
||||||
|
|
||||||
if(!header.Valid()) {
|
if(!header_type.Valid()) {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isStreams{false};
|
||||||
|
|
||||||
|
if(header_type.tocOffset > 0x17000000)
|
||||||
|
isStreams = true;
|
||||||
|
|
||||||
// Read the archive TOC
|
// Read the archive TOC
|
||||||
stream.seekg(header.tocOffset, std::istream::beg);
|
stream.seekg(header_type.tocOffset, std::istream::beg);
|
||||||
for(auto i = 0; i < header.fileCount; ++i) {
|
for(auto i = 0; i < header_type.fileCount; ++i) {
|
||||||
// The first part of the TOC entry is a VLE string,
|
// The first part of the TOC entry is always a VLE string,
|
||||||
// which we don't store inside the type (because we can't)
|
// which we don't store inside the type (because we can't)
|
||||||
//
|
//
|
||||||
// Read this in first.
|
// Read this in first.
|
||||||
auto filename = impl::ReadPString(stream);
|
auto filename = impl::ReadPString(stream);
|
||||||
files[filename].GetTOCEntry() = impl::ReadStreamType<structs::PakTocEntry>(stream);
|
files[filename].InitAs(impl::ReadStreamType<typename T::TocEntry>(stream));
|
||||||
|
|
||||||
|
if(isStreams)
|
||||||
|
files[filename].Visit([&](auto& tocEntry) {
|
||||||
|
tocEntry.creationUnixTime = impl::ReadStreamType<structs::u32>(stream);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
header = header_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PakReader::ReadData() {
|
||||||
|
auto commonHeader = impl::ReadStreamType<structs::PakHeader_Common>(stream);
|
||||||
|
stream.seekg(0, std::istream::beg);
|
||||||
|
|
||||||
|
std::cout << "picking version " << (int)commonHeader.version << '\n';
|
||||||
|
|
||||||
|
switch(commonHeader.version) {
|
||||||
|
case structs::PakVersion::Ver3:
|
||||||
|
ReadData_Impl<structs::PakHeader_V3>();
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver4:
|
||||||
|
ReadData_Impl<structs::PakHeader_V4>();
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver5:
|
||||||
|
ReadData_Impl<structs::PakHeader_V5>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +107,10 @@ namespace europa::io {
|
||||||
if(!fileObject.data.empty())
|
if(!fileObject.data.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& toc = fileObject.GetTOCEntry();
|
fileObject.data.resize(fileObject.GetSize());
|
||||||
fileObject.data.resize(toc.size);
|
|
||||||
|
|
||||||
stream.seekg(toc.offset, std::istream::beg);
|
stream.seekg(fileObject.GetOffset(), std::istream::beg);
|
||||||
stream.read(reinterpret_cast<char*>(&fileObject.data[0]), toc.size);
|
stream.read(reinterpret_cast<char*>(&fileObject.data[0]), fileObject.GetSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
PakReader::MapType& PakReader::GetFiles() {
|
PakReader::MapType& PakReader::GetFiles() {
|
||||||
|
|
|
@ -12,12 +12,13 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "StreamUtils.h"
|
#include "StreamUtils.h"
|
||||||
|
#include "europa/structs/Pak.hpp"
|
||||||
|
|
||||||
namespace europa::io {
|
namespace europa::io {
|
||||||
|
|
||||||
void PakWriter::Init(structs::PakHeader::Version version) {
|
void PakWriter::SetVersion(structs::PakVersion version) {
|
||||||
// for now.
|
// for now.
|
||||||
pakHeader.Init(version);
|
this->version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
// move to a util/ header
|
// move to a util/ header
|
||||||
|
@ -27,28 +28,47 @@ namespace europa::io {
|
||||||
return (-value) & alignment - 1;
|
return (-value) & alignment - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
void PakWriter::Write(std::ostream &os, std::vector<FlattenedType> &&vec, PakProgressReportSink &sink) {
|
||||||
// - Composable operations (WriteTOC, WriteFile, WriteHeader)
|
switch(version) {
|
||||||
|
case structs::PakVersion::Ver3:
|
||||||
|
WriteImpl<structs::PakHeader_V3>(os, std::move(vec), sink);
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver4:
|
||||||
|
WriteImpl<structs::PakHeader_V3>(os, std::move(vec), sink);
|
||||||
|
break;
|
||||||
|
case structs::PakVersion::Ver5:
|
||||||
|
WriteImpl<structs::PakHeader_V3>(os, std::move(vec), sink);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PakWriter::Write(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink) {
|
template<class T>
|
||||||
|
void PakWriter::WriteImpl(std::ostream& os, std::vector<FlattenedType>&& vec, PakProgressReportSink& sink, bool sectorAligned) {
|
||||||
|
|
||||||
std::vector<FlattenedType> sortedFiles = std::move(vec);
|
std::vector<FlattenedType> sortedFiles = std::move(vec);
|
||||||
|
|
||||||
|
T pakHeader{};
|
||||||
|
|
||||||
// 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.GetTOCEntry().size;
|
return elem.second.GetSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Leave space for the header
|
// Leave space for the header
|
||||||
os.seekp(sizeof(structs::PakHeader), std::ostream::beg);
|
os.seekp(sizeof(T), std::ostream::beg);
|
||||||
|
|
||||||
// Version 5 paks seem to have an additional bit of reserved data
|
// Version 5 paks seem to have an additional bit of reserved data
|
||||||
// (which is all zeros.)
|
// (which is all zeros.)
|
||||||
if(pakHeader.version == structs::PakHeader::Version::Ver5) {
|
if(T::VERSION == structs::PakVersion::Ver5) {
|
||||||
os.seekp(6, std::ostream::cur);
|
os.seekp(6, std::ostream::cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//os.seekp(
|
||||||
|
// AlignBy(os.tellp(), 2048),
|
||||||
|
// std::istream::beg
|
||||||
|
//);
|
||||||
|
|
||||||
// Write file data
|
// Write file data
|
||||||
for(auto& [filename, file] : sortedFiles) {
|
for(auto& [filename, file] : sortedFiles) {
|
||||||
sink.OnEvent({
|
sink.OnEvent({
|
||||||
|
@ -56,8 +76,17 @@ namespace europa::io {
|
||||||
filename
|
filename
|
||||||
});
|
});
|
||||||
|
|
||||||
file.GetTOCEntry().offset = os.tellp();
|
|
||||||
os.write(reinterpret_cast<const char*>(file.GetData().data()), file.GetData().size());
|
file.Visit([&](auto& tocEntry) {
|
||||||
|
tocEntry.offset = os.tellp();
|
||||||
|
});
|
||||||
|
|
||||||
|
os.write(reinterpret_cast<const char*>(file.GetData().data()), file.GetSize());
|
||||||
|
|
||||||
|
//os.seekp(
|
||||||
|
// AlignBy(os.tellp(), 2048),
|
||||||
|
// std::istream::beg
|
||||||
|
//);
|
||||||
|
|
||||||
// Flush on file writing
|
// Flush on file writing
|
||||||
os.flush();
|
os.flush();
|
||||||
|
@ -84,7 +113,10 @@ namespace europa::io {
|
||||||
os.put(c);
|
os.put(c);
|
||||||
os.put('\0');
|
os.put('\0');
|
||||||
|
|
||||||
impl::WriteStreamType(os, file.GetTOCEntry());
|
file.Visit([&](auto& tocEntry) {
|
||||||
|
impl::WriteStreamType(os, tocEntry);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,7 +124,6 @@ namespace europa::io {
|
||||||
PakProgressReportSink::PakEvent::Type::FillInHeader
|
PakProgressReportSink::PakEvent::Type::FillInHeader
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "StreamUtils.h"
|
#include "StreamUtils.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace europa::io::impl {
|
namespace europa::io::impl {
|
||||||
|
|
||||||
|
@ -64,4 +65,4 @@ namespace europa::io::impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace europa::io::impl
|
} // namespace europa::io::impl
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace eupak {
|
namespace eupak {
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
createParser.add_argument("-V","--archive-version")
|
createParser.add_argument("-V","--archive-version")
|
||||||
.default_value("starfighter")
|
.default_value("starfighter")
|
||||||
.help(R"(Output archive version. Either "starfighter" or "jedistarfighter".)")
|
.help(R"(Output archive version. Either "pmdl", "starfighter" or "jedistarfighter".)")
|
||||||
.metavar("VERSION");
|
.metavar("VERSION");
|
||||||
|
|
||||||
createParser.add_argument("output")
|
createParser.add_argument("output")
|
||||||
|
@ -123,16 +123,18 @@ int main(int argc, char** argv) {
|
||||||
if(createParser.is_used("--archive-version")) {
|
if(createParser.is_used("--archive-version")) {
|
||||||
const auto& versionStr = createParser.get("--archive-version");
|
const auto& versionStr = createParser.get("--archive-version");
|
||||||
|
|
||||||
if(versionStr == "starfighter") {
|
if(versionStr == "pmdl") {
|
||||||
args.pakVersion = europa::structs::PakHeader::Version::Ver4;
|
args.pakVersion = europa::structs::PakVersion::Ver3;
|
||||||
|
} else if(versionStr == "starfighter") {
|
||||||
|
args.pakVersion = europa::structs::PakVersion::Ver4;
|
||||||
} else if(versionStr == "jedistarfighter") {
|
} else if(versionStr == "jedistarfighter") {
|
||||||
args.pakVersion = europa::structs::PakHeader::Version::Ver5;
|
args.pakVersion = europa::structs::PakVersion::Ver5;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Error: Invalid version \"" << versionStr << "\"\n" << createParser;
|
std::cout << "Error: Invalid version \"" << versionStr << "\"\n" << createParser;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args.pakVersion = europa::structs::PakHeader::Version::Ver4;
|
args.pakVersion = europa::structs::PakVersion::Ver4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ namespace eupak::tasks {
|
||||||
int CreateTask::Run(Arguments&& args) {
|
int CreateTask::Run(Arguments&& args) {
|
||||||
europa::io::PakWriter writer;
|
europa::io::PakWriter writer;
|
||||||
|
|
||||||
writer.Init(args.pakVersion);
|
writer.SetVersion(args.pakVersion);
|
||||||
|
|
||||||
auto currFile = 0;
|
auto currFile = 0;
|
||||||
auto fileCount = 0;
|
auto fileCount = 0;
|
||||||
|
@ -143,9 +143,10 @@ 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.SetData(std::move(pakData));
|
||||||
file.FillTOCEntry();
|
|
||||||
|
|
||||||
file.GetTOCEntry().creationUnixTime = static_cast<std::uint32_t>(lastModified.time_since_epoch().count());
|
file.InitAs(args.pakVersion);
|
||||||
|
|
||||||
|
//file.GetTOCEntry().creationUnixTime = static_cast<std::uint32_t>(lastModified.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();
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace eupak::tasks {
|
||||||
fs::path outputFile;
|
fs::path outputFile;
|
||||||
|
|
||||||
bool verbose;
|
bool verbose;
|
||||||
europa::structs::PakHeader::Version pakVersion;
|
europa::structs::PakVersion pakVersion;
|
||||||
};
|
};
|
||||||
|
|
||||||
int Run(Arguments&& args);
|
int Run(Arguments&& args);
|
||||||
|
|
|
@ -80,7 +80,7 @@ namespace eupak::tasks {
|
||||||
std::cerr << "Extracting file \"" << filename << "\"...\n";
|
std::cerr << "Extracting file \"" << filename << "\"...\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
ofs.write(reinterpret_cast<const char*>(file.GetData().data()), static_cast<std::streampos>(file.GetTOCEntry().size));
|
ofs.write(reinterpret_cast<const char*>(file.GetData().data()), static_cast<std::streampos>(file.GetSize()));
|
||||||
ofs.flush();
|
ofs.flush();
|
||||||
progress.tick();
|
progress.tick();
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,23 +36,35 @@ namespace eupak::tasks {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string version = "Version 4 (Starfighter)";
|
std::visit([&](auto& header){
|
||||||
|
std::string version;
|
||||||
|
if constexpr(std::decay_t<decltype(header)>::VERSION == europa::structs::PakVersion::Ver3)
|
||||||
|
version = "Version 3 (PMDL)";
|
||||||
|
else if constexpr(std::decay_t<decltype(header)>::VERSION == europa::structs::PakVersion::Ver4)
|
||||||
|
version = "Version 4 (Starfighter)";
|
||||||
|
else if constexpr(std::decay_t<decltype(header)>::VERSION == europa::structs::PakVersion::Ver5)
|
||||||
|
version = "Version 5 (Jedi Starfighter)";
|
||||||
|
|
||||||
if(reader.GetHeader().version == europa::structs::PakHeader::Version::Ver5)
|
|
||||||
version = "Version 5 (Jedi Starfighter)";
|
|
||||||
|
|
||||||
std::cout << "Archive " << args.inputPath << ":\n";
|
std::cout << "Archive " << args.inputPath << ":\n";
|
||||||
std::cout << " Created: " << FormatUnixTimestamp(reader.GetHeader().creationUnixTime, DATE_FORMAT) << '\n';
|
std::cout << " Created: " << FormatUnixTimestamp(header.creationUnixTime, DATE_FORMAT) << '\n';
|
||||||
std::cout << " Version: " << version << '\n';
|
std::cout << " Version: " << version << '\n';
|
||||||
std::cout << " Size: " << FormatUnit(reader.GetHeader().tocOffset + reader.GetHeader().tocSize) << '\n';
|
std::cout << " Size: " << FormatUnit(header.tocOffset + header.tocSize) << '\n';
|
||||||
std::cout << " File Count: " << reader.GetHeader().fileCount << " files\n";
|
std::cout << " File Count: " << header.fileCount << " files\n";
|
||||||
|
|
||||||
|
}, reader.GetHeader());
|
||||||
|
|
||||||
|
|
||||||
// Print a detailed file list if verbose.
|
// Print a detailed file list if verbose.
|
||||||
if(args.verbose) {
|
if(args.verbose) {
|
||||||
for(auto& [ filename, file ] : reader.GetFiles()) {
|
for(auto& [ filename, file ] : reader.GetFiles()) {
|
||||||
std::cout << "File \"" << filename << "\":\n";
|
std::cout << "File \"" << filename << "\":\n";
|
||||||
std::cout << " Created: " << FormatUnixTimestamp(file.GetTOCEntry().creationUnixTime, DATE_FORMAT) << '\n';
|
file.Visit([&](auto& tocEntry) {
|
||||||
std::cout << " Size: " << FormatUnit(file.GetTOCEntry().size) << '\n';
|
|
||||||
|
std::cout << " Created: " << FormatUnixTimestamp(tocEntry.creationUnixTime, DATE_FORMAT) << '\n';
|
||||||
|
std::cout << " Size: " << FormatUnit(tocEntry.size) << '\n';
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue