eupak: Implement creation command
Ditto. Also has a cute little progress indicator (more detailed progress will probably have to be done later..) Removes pakcreate, as it's now replaced by eupak for good.
This commit is contained in:
parent
a27ab63c96
commit
e82d693dfc
|
@ -22,6 +22,9 @@ namespace europa::io {
|
|||
struct PakWriter {
|
||||
void Init(structs::PakHeader::Version version);
|
||||
|
||||
// TODO: accessor for header
|
||||
// use flattened vector format anyhow (less allocs, higher perf)
|
||||
|
||||
std::unordered_map<std::string, PakFile>& GetFiles();
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,13 +9,6 @@
|
|||
add_subdirectory(eupak)
|
||||
|
||||
# Most of these utilities are being merged into eupak.
|
||||
|
||||
add_executable(pakcreate pakcreate.cpp)
|
||||
target_link_libraries(pakcreate PUBLIC
|
||||
europa
|
||||
indicators::indicators
|
||||
)
|
||||
|
||||
add_executable(texdump texdump.cpp)
|
||||
target_link_libraries(texdump PUBLIC
|
||||
europa
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
add_executable(eupak
|
||||
main.cpp
|
||||
|
||||
Utils.cpp
|
||||
|
||||
# Tasks
|
||||
tasks/InfoTask.cpp
|
||||
tasks/CreateTask.cpp
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
// MinGW bodges are cool.
|
||||
#if defined(_WIN32) && !defined(_MSC_VER)
|
||||
#define _POSIX_THREAD_SAFE_FUNCTIONS
|
||||
#endif
|
||||
|
||||
#include <Utils.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace eupak {
|
||||
|
||||
std::string FormatUnit(std::uint64_t bytes) {
|
||||
char buf[1024];
|
||||
constexpr auto unit = 1024;
|
||||
|
||||
std::size_t exp {};
|
||||
std::size_t div = unit;
|
||||
|
||||
if(bytes < unit) {
|
||||
sprintf(buf, "%zu B", bytes);
|
||||
return buf;
|
||||
} else {
|
||||
for(std::uint64_t i = bytes / unit; i >= unit; i /= unit) {
|
||||
// Break out if we're gonna set the exponent too high
|
||||
if((exp + 1) > 2)
|
||||
break;
|
||||
|
||||
div *= unit;
|
||||
|
||||
exp++; // TODO: break if too big
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECKED_LIT(literal, expression) (literal)[std::clamp(expression, std::size_t(0), sizeof(literal) - 1)]
|
||||
sprintf(buf, "%0.2f %cB", float(bytes) / float(div), CHECKED_LIT("kMG", exp));
|
||||
#undef CHECKED_LIT
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string FormatUnixTimestamp(std::time_t time, const std::string_view format) {
|
||||
char buf[1024]{};
|
||||
tm tmObject{};
|
||||
|
||||
localtime_r(&time, &tmObject);
|
||||
|
||||
auto count = std::strftime(&buf[0], sizeof(buf), format.data(), &tmObject);
|
||||
|
||||
// an error occured, probably.
|
||||
if(count == -1)
|
||||
return "";
|
||||
|
||||
return { buf, count };
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef EUROPA_EUPAK_UTILS_HPP
|
||||
#define EUROPA_EUPAK_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
|
||||
namespace eupak {
|
||||
|
||||
/**
|
||||
* Format a raw amount of bytes to a human-readable unit, if possible.
|
||||
* \param[in] bytes Size in bytes.
|
||||
*/
|
||||
std::string FormatUnit(std::uint64_t bytes);
|
||||
|
||||
/**
|
||||
* Formats a Unix timestamp using the strftime() C function.
|
||||
*
|
||||
* \param[in] time The Unix timestamp time to format
|
||||
* \param[in] format The format string
|
||||
* \return A formatted string corresponding to user input.
|
||||
*/
|
||||
std::string FormatUnixTimestamp(std::time_t time, const std::string_view format);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // EUROPA_EUPAK_UTILS_HPP
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <tasks/InfoTask.hpp>
|
||||
#include <tasks/ExtractTask.hpp>
|
||||
#include <tasks/CreateTask.hpp>
|
||||
|
||||
#include <argparse/argparse.hpp>
|
||||
|
||||
|
@ -50,9 +51,16 @@ int main(int argc, char** argv) {
|
|||
.metavar("DIRECTORY")
|
||||
.help("Directory to create archive from");
|
||||
|
||||
createParser.add_argument("-V","--archive-version")
|
||||
.default_value("starfighter")
|
||||
.help(R"(Output archive version. Either "starfighter" or "jedistarfighter".)")
|
||||
.metavar("VERSION");
|
||||
|
||||
createParser.add_argument("output")
|
||||
.required()
|
||||
.help("Output archive")
|
||||
.metavar("ARCHIVE");
|
||||
|
||||
createParser.add_argument("--verbose")
|
||||
.help("Increase creation output verbosity")
|
||||
.default_value(false)
|
||||
|
@ -105,9 +113,36 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
if(parser.is_subcommand_used("create")) {
|
||||
std::cout << "Create command is currently unimplemented for now. Use pakcreate until it is\n";
|
||||
eupak::tasks::CreateTask task;
|
||||
eupak::tasks::CreateTask::Arguments args;
|
||||
|
||||
args.verbose = createParser.get<bool>("--verbose");
|
||||
args.inputDirectory = eupak::fs::path(createParser.get("--directory"));
|
||||
args.outputFile = eupak::fs::path(createParser.get("output"));
|
||||
|
||||
if(createParser.is_used("--archive-version")) {
|
||||
auto& versionStr = createParser.get("--archive-version");
|
||||
|
||||
if(versionStr == "starfighter") {
|
||||
args.pakVersion = europa::structs::PakHeader::Version::Ver4;
|
||||
} else if(versionStr == "jedistarfighter") {
|
||||
args.pakVersion = europa::structs::PakHeader::Version::Ver5;
|
||||
} else {
|
||||
std::cout << "Error: Invalid version \"" << versionStr << "\"\n" << createParser;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
args.pakVersion = europa::structs::PakHeader::Version::Ver4;
|
||||
}
|
||||
|
||||
|
||||
if(!eupak::fs::is_directory(args.inputDirectory)) {
|
||||
std::cout << "Error: Provided input isn't a directory\n" << createParser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return task.Run(std::move(args));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -6,4 +6,101 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <europa/io/PakWriter.hpp>
|
||||
#include <fstream>
|
||||
#include <indicators/cursor_control.hpp>
|
||||
#include <indicators/progress_bar.hpp>
|
||||
#include <iostream>
|
||||
#include <tasks/CreateTask.hpp>
|
||||
#include <Utils.hpp>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
int CreateTask::Run(Arguments&& args) {
|
||||
europa::io::PakWriter writer;
|
||||
|
||||
writer.Init(args.pakVersion);
|
||||
|
||||
auto currFile = 0;
|
||||
auto fileCount = 0;
|
||||
|
||||
// Count how many files we're gonna add to the archive
|
||||
for(auto& ent : fs::recursive_directory_iterator(args.inputDirectory)) {
|
||||
if(ent.is_directory())
|
||||
continue;
|
||||
fileCount++;
|
||||
}
|
||||
|
||||
std::cout << "Going to write " << fileCount << " files into " << args.outputFile << '\n';
|
||||
|
||||
indicators::ProgressBar progress {
|
||||
indicators::option::BarWidth { 50 },
|
||||
indicators::option::ForegroundColor { indicators::Color::green },
|
||||
indicators::option::MaxProgress { fileCount },
|
||||
indicators::option::ShowPercentage { true },
|
||||
indicators::option::ShowElapsedTime { true },
|
||||
indicators::option::ShowRemainingTime { true },
|
||||
|
||||
indicators::option::PrefixText { "Creating archive " }
|
||||
};
|
||||
|
||||
indicators::show_console_cursor(false);
|
||||
|
||||
// TODO: use time to write in the header
|
||||
// also: is there any point to verbosity? could add archive written size ig
|
||||
|
||||
for(auto& ent : fs::recursive_directory_iterator(args.inputDirectory)) {
|
||||
if(ent.is_directory())
|
||||
continue;
|
||||
|
||||
auto relativePathName = fs::relative(ent.path(), args.inputDirectory).string();
|
||||
auto lastModified = fs::last_write_time(ent.path());
|
||||
|
||||
// Convert to Windows path separator always (that's what the game wants, after all)
|
||||
for(auto& c : relativePathName)
|
||||
if(c == '/')
|
||||
c = '\\';
|
||||
|
||||
|
||||
progress.set_option(indicators::option::PostfixText { relativePathName + " (" + std::to_string(currFile + 1) + '/' + std::to_string(fileCount) + ")"});
|
||||
|
||||
std::ifstream ifs(ent.path(), std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
std::cout << "Error: Couldn't open file for archive path \"" << relativePathName << "\"\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
europa::io::PakFile file;
|
||||
europa::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();
|
||||
|
||||
file.GetTOCEntry().creationUnixTime = static_cast<std::uint32_t>(lastModified.time_since_epoch().count());
|
||||
|
||||
writer.GetFiles()[relativePathName] = std::move(file);
|
||||
|
||||
progress.tick();
|
||||
currFile++;
|
||||
}
|
||||
|
||||
std::ofstream ofs(args.outputFile.string(), std::ofstream::binary);
|
||||
|
||||
if(!ofs) {
|
||||
std::cout << "Error: Couldn't open " << args.outputFile << " for writing\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
writer.Write(ofs);
|
||||
indicators::show_console_cursor(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace eupak::tasks
|
|
@ -11,13 +11,21 @@
|
|||
|
||||
#include <CommonDefs.hpp>
|
||||
|
||||
#include <europa/structs/Pak.hpp>
|
||||
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
struct CreateTask {
|
||||
struct Arguments {
|
||||
fs::path inputDirectory;
|
||||
fs::path outputFile;
|
||||
|
||||
bool verbose;
|
||||
europa::structs::PakHeader::Version pakVersion;
|
||||
};
|
||||
|
||||
int Run(Arguments&& args);
|
||||
};
|
||||
|
||||
} // namespace europa
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
namespace eupak::tasks {
|
||||
|
||||
int ExtractTask::Run(ExtractTask::Arguments&& args) {
|
||||
int ExtractTask::Run(Arguments&& args) {
|
||||
std::ifstream ifs(args.inputPath.string(), std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
|
|
|
@ -6,12 +6,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
|
||||
// MinGW bodges are cool.
|
||||
#if defined(_WIN32) && !defined(_MSC_VER)
|
||||
#define _POSIX_THREAD_SAFE_FUNCTIONS
|
||||
#endif
|
||||
|
||||
#include <tasks/InfoTask.hpp>
|
||||
|
||||
#include <europa/io/PakReader.hpp>
|
||||
|
@ -19,55 +13,13 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <Utils.hpp>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Format a raw amount of bytes to a human-readable unit.
|
||||
* \param[in] bytes Size in bytes.
|
||||
*/
|
||||
std::string FormatUnit(std::uint64_t bytes) {
|
||||
char buf[1024];
|
||||
constexpr auto unit = 1024;
|
||||
|
||||
std::size_t exp {};
|
||||
std::size_t div = unit;
|
||||
|
||||
if(bytes < unit) {
|
||||
sprintf(buf, "%zu B", bytes);
|
||||
return buf;
|
||||
} else {
|
||||
for(std::uint64_t i = bytes / unit; i >= unit; i /= unit) {
|
||||
div *= unit;
|
||||
exp++; // TODO: break if too big
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECKED_LIT(literal, expression) (literal)[std::clamp(expression, std::size_t(0), sizeof(literal) - 1)]
|
||||
sprintf(buf, "%0.2f %cB", float(bytes) / float(div), CHECKED_LIT("kMG", exp));
|
||||
#undef CHECKED_LIT
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string FormatUnixTimestamp(time_t time, const std::string_view format) {
|
||||
char buf[1024]{};
|
||||
tm tmObject{};
|
||||
|
||||
localtime_r(&time, &tmObject);
|
||||
|
||||
auto count = std::strftime(&buf[0], sizeof(buf), format.data(), &tmObject);
|
||||
|
||||
// an error occured, probably.
|
||||
if(count == -1)
|
||||
return "";
|
||||
|
||||
return { buf, count };
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static auto DATE_FORMAT = "%m/%d/%Y %r";
|
||||
|
||||
int InfoTask::Run(InfoTask::Arguments&& args) {
|
||||
int InfoTask::Run(Arguments&& args) {
|
||||
std::ifstream ifs(args.inputPath.string(), std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
// A test utility to regurgitate a pak.
|
||||
|
||||
#include <europa/io/PakReader.hpp>
|
||||
#include <europa/io/PakWriter.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
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::PakHeader::Version::Ver5);
|
||||
}
|
||||
} else {
|
||||
std::cout << "Writing Starfighter archive\n";
|
||||
writer.Init(structs::PakHeader::Version::Ver4);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
file.GetTOCEntry().creationUnixTime = 0;
|
||||
|
||||
//std::cout << "File \"" << relativePathName << "\"\n";
|
||||
writer.GetFiles()[relativePathName] = std::move(file);
|
||||
}
|
||||
|
||||
writer.Write(ofs);
|
||||
|
||||
std::cout << "Wrote archive to \"" << argv[2] << "\"!\n";
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue