*: Introduce "eupak" utility
I have been preparing for this for a while. Instead of having a bunch of strewn out utilities, let's just have one solid multitool which is nice to use. This commit also removes europa_pack_extractor, as it's now unnesscary and replaced with a better utility, that does more. Creation wasn't implemented yet, but I really need to sleep. It can be done later, and pakcreate can be used as a temporary stopgap.
This commit is contained in:
parent
87b02d8659
commit
a95d104e7f
|
@ -16,7 +16,7 @@ endif()
|
|||
include(cmake/Policies.cmake)
|
||||
|
||||
project(EuropaTools
|
||||
VERSION 0.0.1 # Placeholder for sem-ver usage. Replace with real value later.
|
||||
VERSION 1.0.0
|
||||
LANGUAGES C CXX
|
||||
DESCRIPTION "Tools for working with LEC Europa based games (Star Wars: Starfighter & Star Wars: Jedi Starfighter)"
|
||||
)
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace europa::io {
|
|||
MapType& GetFiles();
|
||||
const MapType& GetFiles() const;
|
||||
|
||||
// implement in cpp later, lazy and just wanna get this out :vvv
|
||||
const structs::PakHeader& GetHeader() const { return header; }
|
||||
|
||||
private:
|
||||
std::istream& stream;
|
||||
bool invalid { false };
|
||||
|
|
|
@ -6,28 +6,22 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
add_executable(europa_pack_extractor europa_pack_extractor.cpp)
|
||||
add_subdirectory(eupak)
|
||||
|
||||
target_link_libraries(europa_pack_extractor PUBLIC
|
||||
europa
|
||||
indicators::indicators
|
||||
)
|
||||
# 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
|
||||
)
|
||||
|
||||
add_executable(paktest paktest.cpp)
|
||||
|
||||
target_link_libraries(paktest PUBLIC
|
||||
europa
|
||||
)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# EuropaTools
|
||||
#
|
||||
# (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
add_executable(eupak
|
||||
main.cpp
|
||||
|
||||
# Tasks
|
||||
tasks/InfoTask.cpp
|
||||
tasks/CreateTask.cpp
|
||||
tasks/ExtractTask.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(eupak PUBLIC
|
||||
europa
|
||||
argparse::argparse
|
||||
indicators::indicators
|
||||
)
|
||||
|
||||
configure_file(EupakConfig.hpp.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/EupakConfig.hpp
|
||||
)
|
||||
|
||||
target_include_directories(eupak PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(eupak PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef EUROPA_EUPAK_COMMONDEFS_HPP
|
||||
#define EUROPA_EUPAK_COMMONDEFS_HPP
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace eupak {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
}
|
||||
|
||||
#endif // EUROPA_EUPAK_COMMONDEFS_HPP
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#define EUPAK_VERSION_STR "@PROJECT_VERSION@"
|
||||
|
||||
#define EUPAK_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define EUPAK_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define EUPAK_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <EupakConfig.hpp>
|
||||
|
||||
#include <tasks/InfoTask.hpp>
|
||||
#include <tasks/ExtractTask.hpp>
|
||||
|
||||
#include <argparse/argparse.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
argparse::ArgumentParser parser("eupak", EUPAK_VERSION_STR);
|
||||
parser.add_description("Eupak (Europa Package Multi-Tool) v" EUPAK_VERSION_STR);
|
||||
|
||||
argparse::ArgumentParser infoParser("info", EUPAK_VERSION_STR, argparse::default_arguments::help);
|
||||
infoParser.add_description("Print information about a package file.");
|
||||
infoParser.add_argument("input")
|
||||
.help("Input archive")
|
||||
.metavar("ARCHIVE");
|
||||
|
||||
infoParser.add_argument("--verbose")
|
||||
.help("Increase information output verbosity (print a list of files).")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
argparse::ArgumentParser extractParser("extract", EUPAK_VERSION_STR, argparse::default_arguments::help);
|
||||
extractParser.add_description("Extract a package file.");
|
||||
extractParser.add_argument("-d", "--directory")
|
||||
.default_value("")
|
||||
.metavar("DIRECTORY")
|
||||
.help("Directory to extract to.");
|
||||
extractParser.add_argument("input")
|
||||
.help("Input archive")
|
||||
.metavar("ARCHIVE");
|
||||
|
||||
extractParser.add_argument("--verbose")
|
||||
.help("Increase extraction output verbosity")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
argparse::ArgumentParser createParser("create", EUPAK_VERSION_STR, argparse::default_arguments::help);
|
||||
createParser.add_description("Create a package file.");
|
||||
createParser.add_argument("-d", "--directory")
|
||||
.required()
|
||||
.metavar("DIRECTORY")
|
||||
.help("Directory to create archive from");
|
||||
|
||||
createParser.add_argument("output")
|
||||
.help("Output archive")
|
||||
.metavar("ARCHIVE");
|
||||
createParser.add_argument("--verbose")
|
||||
.help("Increase creation output verbosity")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
|
||||
parser.add_subparser(infoParser);
|
||||
parser.add_subparser(extractParser);
|
||||
parser.add_subparser(createParser);
|
||||
|
||||
try {
|
||||
parser.parse_args(argc, argv);
|
||||
} catch(std::runtime_error& error) {
|
||||
std::cout << error.what() << '\n' << parser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run the given task
|
||||
|
||||
if(parser.is_subcommand_used("extract")) {
|
||||
eupak::tasks::ExtractTask task;
|
||||
eupak::tasks::ExtractTask::Arguments args;
|
||||
|
||||
args.verbose = extractParser.get<bool>("--verbose");
|
||||
args.inputPath = eupak::fs::path(extractParser.get("input"));
|
||||
|
||||
if(extractParser.is_used("--directory")) {
|
||||
args.outputDirectory = eupak::fs::path(extractParser.get("--directory"));
|
||||
} else {
|
||||
// Default to the basename appended to current path
|
||||
// as a "relatively sane" default path to extract to.
|
||||
// Should be okay.
|
||||
args.outputDirectory = eupak::fs::current_path() / args.inputPath.stem();
|
||||
}
|
||||
|
||||
std::cout << "Input PAK/PMDL: " << args.inputPath << '\n';
|
||||
std::cout << "Output Directory: " << args.outputDirectory << '\n';
|
||||
|
||||
return task.Run(std::move(args));
|
||||
}
|
||||
|
||||
if(parser.is_subcommand_used("info")) {
|
||||
eupak::tasks::InfoTask task;
|
||||
eupak::tasks::InfoTask::Arguments args;
|
||||
|
||||
args.verbose = infoParser.get<bool>("--verbose");
|
||||
args.inputPath = eupak::fs::path(infoParser.get("input"));
|
||||
|
||||
return task.Run(std::move(args));
|
||||
}
|
||||
|
||||
if(parser.is_subcommand_used("create")) {
|
||||
std::cout << "Create command is currently unimplemented for now. Use pakcreate until it is\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <tasks/CreateTask.hpp>
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef EUROPA_EUPAK_TASKS_CREATETASK_HPP
|
||||
#define EUROPA_EUPAK_TASKS_CREATETASK_HPP
|
||||
|
||||
#include <CommonDefs.hpp>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
struct CreateTask {
|
||||
struct Arguments {
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} // namespace europa
|
||||
|
||||
#endif // EUROPA_EUPAK_TASKS_CREATETASK_HPP
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <tasks/ExtractTask.hpp>
|
||||
|
||||
#include <europa/io/PakReader.hpp>
|
||||
#include <fstream>
|
||||
#include <indicators/cursor_control.hpp>
|
||||
#include <indicators/progress_bar.hpp>
|
||||
#include <iostream>
|
||||
|
||||
// this actually is pretty fast so maybe I won't bother doing crazy thread optimizations..
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
int ExtractTask::Run(ExtractTask::Arguments&& args) {
|
||||
std::ifstream ifs(args.inputPath.string(), std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
std::cout << "Error: Could not open file " << args.inputPath << ".\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
europa::io::PakReader reader(ifs);
|
||||
|
||||
reader.ReadData();
|
||||
|
||||
if(reader.Invalid()) {
|
||||
std::cout << "Error: Invalid PAK/PMDL file " << args.inputPath << ".\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
indicators::ProgressBar progress {
|
||||
indicators::option::BarWidth { 50 },
|
||||
indicators::option::ForegroundColor { indicators::Color::green },
|
||||
indicators::option::MaxProgress { reader.GetFiles().size() },
|
||||
indicators::option::ShowPercentage { true },
|
||||
indicators::option::ShowElapsedTime { true },
|
||||
indicators::option::ShowRemainingTime { true },
|
||||
|
||||
indicators::option::PrefixText { "Extracting archive " }
|
||||
};
|
||||
|
||||
indicators::show_console_cursor(false);
|
||||
|
||||
for(auto& [filename, file] : reader.GetFiles()) {
|
||||
auto nameCopy = filename;
|
||||
|
||||
#ifndef _WIN32
|
||||
if(nameCopy.find('\\') != std::string::npos) {
|
||||
// Grody, but eh. Should work.
|
||||
for(auto& c : nameCopy)
|
||||
if(c == '\\')
|
||||
c = '/';
|
||||
}
|
||||
#endif
|
||||
|
||||
progress.set_option(indicators::option::PostfixText { filename });
|
||||
|
||||
auto outpath = (args.outputDirectory / nameCopy);
|
||||
|
||||
if(!fs::exists(outpath.parent_path()))
|
||||
fs::create_directories(outpath.parent_path());
|
||||
|
||||
reader.ReadFile(filename);
|
||||
|
||||
std::ofstream ofs(outpath.string(), std::ofstream::binary);
|
||||
|
||||
if(!ofs) {
|
||||
std::cerr << "Could not open " << outpath << " for writing.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(args.verbose) {
|
||||
std::cerr << "Extracting file \"" << filename << "\"...\n";
|
||||
}
|
||||
|
||||
ofs.write(reinterpret_cast<const char*>(file.GetData().data()), static_cast<std::streampos>(file.GetTOCEntry().size));
|
||||
ofs.flush();
|
||||
progress.tick();
|
||||
}
|
||||
|
||||
indicators::show_console_cursor(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef EUROPA_EUPAK_TASKS_EXTRACTTASK_HPP
|
||||
#define EUROPA_EUPAK_TASKS_EXTRACTTASK_HPP
|
||||
|
||||
#include <CommonDefs.hpp>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
struct ExtractTask {
|
||||
|
||||
struct Arguments {
|
||||
fs::path inputPath;
|
||||
fs::path outputDirectory;
|
||||
bool verbose;
|
||||
};
|
||||
|
||||
int Run(Arguments&& args);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // EUROPATOOLS_EXTRACTTASK_H
|
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <tasks/InfoTask.hpp>
|
||||
|
||||
#include <europa/io/PakReader.hpp>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
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) {
|
||||
std::ifstream ifs(args.inputPath.string(), std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
std::cout << "Error: Could not open file " << args.inputPath << ".\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
europa::io::PakReader reader(ifs);
|
||||
|
||||
reader.ReadData();
|
||||
|
||||
if(reader.Invalid()) {
|
||||
std::cout << "Error: Invalid PAK/PMDL file " << args.inputPath << ".\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string version = "Version 4 (Starfighter)";
|
||||
|
||||
if(reader.GetHeader().version == europa::structs::PakHeader::Version::Ver5)
|
||||
version = "Version 5 (Jedi Starfighter)";
|
||||
|
||||
std::cout << "Archive " << args.inputPath << ":\n";
|
||||
std::cout << " Created: " << FormatUnixTimestamp(reader.GetHeader().creationUnixTime, DATE_FORMAT) << '\n';
|
||||
std::cout << " Version: " << version << '\n';
|
||||
std::cout << " Size: " << FormatUnit(reader.GetHeader().tocOffset + reader.GetHeader().tocSize) << '\n';
|
||||
std::cout << " File Count: " << reader.GetHeader().fileCount << " files\n";
|
||||
|
||||
// Print a detailed file list if verbose.
|
||||
if(args.verbose) {
|
||||
for(auto& [ filename, file ] : reader.GetFiles()) {
|
||||
std::cout << "File \"" << filename << "\":\n";
|
||||
std::cout << " Created: " << FormatUnixTimestamp(file.GetTOCEntry().creationUnixTime, DATE_FORMAT) << '\n';
|
||||
std::cout << " Size: " << FormatUnit(file.GetTOCEntry().size) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef EUROPA_EUPAK_TASKS_INFOTASK_HPP
|
||||
#define EUROPA_EUPAK_TASKS_INFOTASK_HPP
|
||||
|
||||
#include <CommonDefs.hpp>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
struct InfoTask {
|
||||
|
||||
struct Arguments {
|
||||
fs::path inputPath;
|
||||
bool verbose;
|
||||
};
|
||||
|
||||
int Run(Arguments&& args);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EUROPA_EUPAK_TASKS_INFOTASK_HPP
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <europa/io/PakReader.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <indicators/cursor_control.hpp>
|
||||
#include <indicators/progress_bar.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if(argc != 2) {
|
||||
std::cout << "Usage: " << argv[0] << " [path to Europa PAK file]";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ifstream ifs(argv[1], std::ifstream::binary);
|
||||
|
||||
if(!ifs) {
|
||||
std::cout << "Invalid file \"" << argv[1] << "\"\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
europa::io::PakReader reader(ifs);
|
||||
|
||||
auto baseDirectory = fs::path(argv[1]).stem();
|
||||
|
||||
reader.ReadData();
|
||||
|
||||
if(reader.Invalid()) {
|
||||
std::cout << "Invalid pak data in file \"" << argv[1] << "\"\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
indicators::ProgressBar progress {
|
||||
indicators::option::BarWidth { 50 },
|
||||
indicators::option::ForegroundColor { indicators::Color::green },
|
||||
indicators::option::MaxProgress { reader.GetFiles().size() },
|
||||
indicators::option::ShowPercentage { true },
|
||||
indicators::option::ShowElapsedTime { true },
|
||||
indicators::option::ShowRemainingTime { true },
|
||||
|
||||
indicators::option::PrefixText { "Extracting archive " }
|
||||
};
|
||||
|
||||
indicators::show_console_cursor(false);
|
||||
|
||||
for(auto& [filename, file] : reader.GetFiles()) {
|
||||
auto nameCopy = filename;
|
||||
|
||||
#ifndef _WIN32
|
||||
if(nameCopy.find('\\') != std::string::npos) {
|
||||
// Grody, but eh. Should work.
|
||||
for(auto& c : nameCopy)
|
||||
if(c == '\\')
|
||||
c = '/';
|
||||
}
|
||||
#endif
|
||||
|
||||
progress.set_option(indicators::option::PostfixText { filename });
|
||||
|
||||
auto outpath = (baseDirectory / nameCopy);
|
||||
|
||||
if(!fs::exists(outpath.parent_path()))
|
||||
fs::create_directories(outpath.parent_path());
|
||||
|
||||
reader.ReadFile(filename);
|
||||
|
||||
std::ofstream ofs(outpath.string(), std::ofstream::binary);
|
||||
|
||||
if(!ofs) {
|
||||
std::cerr << "Could not open \"" << outpath.string() << "\" for writing.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
ofs.write(reinterpret_cast<const char*>(file.GetData().data()), static_cast<std::streampos>(file.GetTOCEntry().size));
|
||||
progress.tick();
|
||||
}
|
||||
|
||||
indicators::show_console_cursor(true);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue