Fix io::PakWriter, add pakcreate

Also improves paktest.

Pakcreate is a new utility for creating completely new archives from a directory.

Usage:
pakcreate [input path] [output .PAK/pmdl filename] [--jedi]

Options:
--jedi : Make Jedi Starfighter archive
This commit is contained in:
Lily Tsuru 2022-09-15 04:11:53 -05:00
parent 81dad9965c
commit 4452b87780
5 changed files with 134 additions and 18 deletions

View File

@ -36,13 +36,11 @@ namespace europa::structs {
u32 tocOffset; u32 tocOffset;
// Dunno what this is u32 tocSize;
u32 unk;
u32 fileCount; u32 fileCount;
// Could be Windows FILETIME? u32 creationUnixTime;
u32 unk2;
u32 reservedPad; u32 reservedPad;
@ -97,10 +95,7 @@ namespace europa::structs {
struct [[gnu::packed]] PakTocEntry { struct [[gnu::packed]] PakTocEntry {
u32 offset; u32 offset;
u32 size; u32 size;
u32 creationUnixTime;
// Seems to be the same as the header's
// unk2?
u32 unk3;
}; };
static_assert(sizeof(PakHeader) == 0x29, "PakHeader wrong size!!"); static_assert(sizeof(PakHeader) == 0x29, "PakHeader wrong size!!");

View File

@ -24,14 +24,13 @@ namespace europa::io {
} }
void PakWriter::Write(std::ostream& os) { void PakWriter::Write(std::ostream& os) {
// Set up the header a bit more...
pakHeader.fileCount = archiveFiles.size(); pakHeader.fileCount = archiveFiles.size();
// Leave space for the header // Leave space for the header
os.seekp(sizeof(structs::PakHeader), std::ostream::beg); os.seekp(sizeof(structs::PakHeader), std::ostream::beg);
// Seek forwards for version 2 PAKs, as the only // Seek forwards for version 2 PAKs, as the only
// difference seems to be // difference seems to be this additional bump
if(pakHeader.version == structs::PakVersion::Ver2) { if(pakHeader.version == structs::PakVersion::Ver2) {
os.seekp(6, std::ostream::cur); os.seekp(6, std::ostream::cur);
} }
@ -57,6 +56,9 @@ namespace europa::io {
impl::WriteStreamType(os, file.GetTOCEntry()); impl::WriteStreamType(os, file.GetTOCEntry());
} }
// Fill out the TOC size.
pakHeader.tocSize = static_cast<std::uint32_t>(os.tellp()) - (pakHeader.tocOffset - 1);
os.seekp(0, std::ostream::beg); os.seekp(0, std::ostream::beg);
impl::WriteStreamType(os, pakHeader); impl::WriteStreamType(os, pakHeader);
} }

View File

@ -1,6 +1,7 @@
add_executable(europa_pack_extractor europa_pack_extractor.cpp) add_executable(europa_pack_extractor europa_pack_extractor.cpp)
target_link_libraries(europa_pack_extractor PUBLIC libeuropa target_link_libraries(europa_pack_extractor PUBLIC
libeuropa
indicators::indicators indicators::indicators
) )
@ -9,10 +10,24 @@ set_target_properties(europa_pack_extractor PROPERTIES
CXX_STANDARD_REQUIRED ON CXX_STANDARD_REQUIRED ON
) )
add_executable(pakcreate pakcreate.cpp)
target_link_libraries(pakcreate PUBLIC
libeuropa
indicators::indicators
)
set_target_properties(pakcreate PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
add_executable(texdump texdump.cpp) add_executable(texdump texdump.cpp)
target_link_libraries(texdump PUBLIC libeuropa) target_link_libraries(texdump PUBLIC
libeuropa
)
set_target_properties(texdump PROPERTIES set_target_properties(texdump PROPERTIES
CXX_STANDARD 20 CXX_STANDARD 20
@ -21,7 +36,9 @@ set_target_properties(texdump PROPERTIES
add_executable(paktest paktest.cpp) add_executable(paktest paktest.cpp)
target_link_libraries(paktest PUBLIC libeuropa) target_link_libraries(paktest PUBLIC
libeuropa
)
set_target_properties(paktest PROPERTIES set_target_properties(paktest PROPERTIES
CXX_STANDARD 20 CXX_STANDARD 20

80
src/tools/pakcreate.cpp Normal file
View File

@ -0,0 +1,80 @@
//
// EuropaTools
//
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// A test utility to regurgitate a pak.
#include <europa/io/PakReader.h>
#include <europa/io/PakWriter.h>
#include <fstream>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
using namespace europa;
int main(int argc, char** argv) {
std::ofstream ofs(argv[2], std::ofstream::binary);
if(!ofs) {
std::cout << "Couldn't open output PAK file\n";
return 1;
}
io::PakWriter writer;
if(argv[3] != nullptr) {
if(!strcmp(argv[3], "--jedi")) {
std::cout << "Writing Jedi Starfighter archive\n";
writer.Init(structs::PakVersion::Ver2);
}
} else {
std::cout << "Writing Starfighter archive\n";
writer.Init(structs::PakVersion::Starfighter);
}
for(auto& ent : fs::recursive_directory_iterator(argv[1])) {
if(ent.is_directory())
continue;
auto relativePathName = fs::relative(ent.path(), argv[1]).string();
// Convert to Windows path separator always (that's what the game wants, after all)
for(auto& c : relativePathName)
if(c == '/')
c = '\\';
std::ifstream ifs(ent.path(), std::ifstream::binary);
if(!ifs) {
std::cout << "ERROR: Couldn't open file for archive path \"" << relativePathName << "\"\n";
return 1;
}
io::PakFile file;
io::PakFile::DataType pakData;
ifs.seekg(0, std::ifstream::end);
pakData.resize(ifs.tellg());
ifs.seekg(0, std::ifstream::beg);
ifs.read(reinterpret_cast<char*>(&pakData[0]), pakData.size());
file.SetData(std::move(pakData));
file.FillTOCEntry();
std::cout << "Added \"" << relativePathName << "\"\n";
writer.GetFiles()[relativePathName] = std::move(file);
}
writer.Write(ofs);
std::cout << "Wrote archive to \"" << argv[2] << "\"!\n";
return 0;
}

View File

@ -14,29 +14,51 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
using namespace europa;
int main(int argc, char** argv) { int main(int argc, char** argv) {
std::ifstream ifs(argv[1], std::ifstream::binary); std::ifstream ifs(argv[1], std::ifstream::binary);
std::ofstream ofs("new_archive.pak", std::ofstream::binary); std::ofstream ofs(argv[2], std::ofstream::binary);
europa::io::PakWriter writer; if(!ifs) {
std::cout << "Couldn't open input PAK file\n";
return 1;
}
writer.Init(europa::structs::PakVersion::Ver2); io::PakWriter writer;
if(argv[3] != nullptr) {
if(!strcmp(argv[3], "--jedi")) {
std::cout << "Writing Jedi Starfighter archive\n";
writer.Init(structs::PakVersion::Ver2);
}
} else {
std::cout << "Writing Starfighter archive\n";
writer.Init(structs::PakVersion::Starfighter);
}
// Read pak data and vomit it into the writer. // Read pak data and vomit it into the writer.
// This will temporarily consume 2x the memory (so about 240mb for the biggest paks I've seen), // This will temporarily consume 2x the memory (so about 240mb for the biggest paks I've seen),
// but the writer will contain the first copy, // but the writer will contain the first copy,
// until it's cleared. // until it's cleared.
{ {
europa::io::PakReader reader(ifs); io::PakReader reader(ifs);
reader.ReadData(); reader.ReadData();
if(reader.Invalid()) {
std::cout << "Invalid pak file, exiting\n";
return 1;
}
for(auto& [filename, file] : reader.GetFiles()) { for(auto& [filename, file] : reader.GetFiles()) {
std::cout << "Reading \"" << filename << "\" into memory\n";
reader.ReadFile(filename);
writer.GetFiles()[filename] = file; writer.GetFiles()[filename] = file;
} }
} }
writer.Write(ofs); writer.Write(ofs);
std::cout << "Wrote regurgitated archive to new.pak!\n"; std::cout << "Wrote regurgitated archive to \"" << argv[2] << "\"!\n";
return 0; return 0;
} }