From e070ec044e7844a1aaabff0a74d76e10509bf7e8 Mon Sep 17 00:00:00 2001 From: stevenhowes Date: Wed, 30 Oct 2024 08:34:11 +0000 Subject: [PATCH] First version --- Makefile | 36 ++++++++++++ PakFS/PakFS.cpp | 94 ++++++++++++++++++++++++++++++ PakFS/PakFS.hpp | 29 +++++++++ PakFS/PakFile.cpp | 57 ++++++++++++++++++ PakFS/PakFile.hpp | 41 +++++++++++++ README.md | 12 +++- base/folder2/subfolder1/testfile5 | 1 + base/folder2/testfile4 | 1 + base/pak0.pak | Bin 0 -> 337 bytes base/pak2.pak | Bin 0 -> 139 bytes base/testfile | 1 + example1.cpp | 28 +++++++++ example2.cpp | 37 ++++++++++++ 13 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 PakFS/PakFS.cpp create mode 100644 PakFS/PakFS.hpp create mode 100644 PakFS/PakFile.cpp create mode 100644 PakFS/PakFile.hpp create mode 100644 base/folder2/subfolder1/testfile5 create mode 100644 base/folder2/testfile4 create mode 100644 base/pak0.pak create mode 100644 base/pak2.pak create mode 100644 base/testfile create mode 100644 example1.cpp create mode 100644 example2.cpp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..94459e0 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +TARGET1 = example1 +TARGET2 = example2 + +SRCS_TARGET1 = example1.cpp $(wildcard PakFS/*.cpp) +SRCS_TARGET2 = example2.cpp $(wildcard PakFS/*.cpp) + +CXX = g++ +CXXFLAGS = -Wall -Wextra + +OBJ_DIR := ./objects +OBJS_TARGET1 := $(SRCS_TARGET1:%.cpp=$(OBJ_DIR)/%.o) +OBJS_TARGET2 := $(SRCS_TARGET2:%.cpp=$(OBJ_DIR)/%.o) + +all: build $(TARGET1) $(TARGET2) + +build: + @mkdir -p ./$(OBJ_DIR)/PakFS +valgrind: all + valgrind ./$(TARGET1) + valgrind ./$(TARGET2) + +$(TARGET1): $(OBJS_TARGET1) + $(CXX) $(CXXFLAGS) -o $(TARGET1) $(OBJS_TARGET1) + +$(TARGET2): $(OBJS_TARGET2) + $(CXX) $(CXXFLAGS) -o $(TARGET2) $(OBJS_TARGET2) + +$(OBJ_DIR)/%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -f $(TARGET1) $(TARGET2) + rm -rf $(OBJ_DIR) + + +.PHONY: all clean diff --git a/PakFS/PakFS.cpp b/PakFS/PakFS.cpp new file mode 100644 index 0000000..f07d9cf --- /dev/null +++ b/PakFS/PakFS.cpp @@ -0,0 +1,94 @@ +#include "PakFS.hpp" +#include +#include + +PakFS::PakFS(const std::string& baseDir, bool paksOnly) { + base = baseDir; + + for(int i = 0; i < MAX_PAKS; i++) { + std::string path = baseDir + "/pak" + std::to_string(i) + ".pak"; + + if(!std::ifstream (path)) { + pakFiles[i] = NULL; + continue; + } + + pakFiles[i] = new PakFile(path); + + for (const auto& file : pakFiles[i]->getFileNames()) { + files[file] = i; + } + } + + if (!paksOnly) { + ParseDirectory(""); + } +} + +PakFS::PakFS(const std::string& baseDir) : PakFS(baseDir, true) {} + +PakFS::~PakFS() { + for (int i = 0; i < MAX_PAKS; i++) { + if (pakFiles[i]) { + delete pakFiles[i]; + } + } +} + +int PakFS::getFileSize(const std::string& t_filename) { + if(files[t_filename] == -1) { + std::ifstream file(base + "/" + t_filename, std::ios::binary | std::ios::ate); + return file.tellg(); + }else{ + return pakFiles[files[t_filename]]->getFileSize(t_filename); + } +} + +void PakFS::getFile(const std::string& t_filename, uint8_t* buffer) { + if(files[t_filename] == -1) { + std::string filename = base + "/" + t_filename; + std::ifstream file(filename, std::ios::binary); + file.read(reinterpret_cast(buffer), getFileSize(t_filename)); + return; + }else{ + pakFiles[files[t_filename]]->getFile(t_filename, buffer); + } +} + +std::string PakFS::getFileString(const std::string& t_filename) { + int size = getFileSize(t_filename); + uint8_t* ByteArray; + ByteArray = new uint8_t[size + 1]; + getFile(t_filename, ByteArray); + ByteArray[size] = 0; + std::string str(reinterpret_cast(ByteArray)); + delete [] ByteArray; + return str; +} + +void PakFS::listFiles() { + std::cout << "--------------" << std::endl << "PAK | Filename" << std::endl << "--------------" << std::endl; + for (const auto& file : files) { + std::cout << std::setw(3) << std::setfill(' ') << file.second << " | " << file.first << std::endl; + } + std::cout << std::endl; +} + +void PakFS::ParseDirectory(const std::string& t_path) { + std::string path = t_path; + + if(!path.empty()) { + path += "/"; + } + + for (const auto& entry : std::filesystem::directory_iterator(base + "/" + t_path)) { + if (entry.is_regular_file()) { + if (entry.path().extension().string() == ".pak") { + continue; + } + files[path + entry.path().filename().string()] = -1; + }else if(entry.is_directory()){ + ParseDirectory(entry.path().string().substr(base.size() + 1)); + } + } +} \ No newline at end of file diff --git a/PakFS/PakFS.hpp b/PakFS/PakFS.hpp new file mode 100644 index 0000000..eac213f --- /dev/null +++ b/PakFS/PakFS.hpp @@ -0,0 +1,29 @@ +#ifndef PAKFS_HPP +#define PAKFS_HPP + +#include "PakFile.hpp" +#include + +#define MAX_PAKS 10 + +class PakFS { +public: + PakFS(const std::string& baseDir, bool paksOnly); + PakFS(const std::string& baseDir); + ~PakFS(); + + int getFileSize(const std::string& filename); + void getFile(const std::string& filename, uint8_t* buffer); + std::string getFileString(const std::string& filename); + void listFiles(); + +private: + std::map files; + PakFile* pakFiles[MAX_PAKS]; + std::string base {""}; + + void ParseDirectory(const std::string& path); + +}; + +#endif // PAKFS_HPP \ No newline at end of file diff --git a/PakFS/PakFile.cpp b/PakFS/PakFile.cpp new file mode 100644 index 0000000..4d35682 --- /dev/null +++ b/PakFS/PakFile.cpp @@ -0,0 +1,57 @@ +#include "PakFile.hpp" + +PakFile::PakFile(const std::string& filename) { + file.open(filename, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open PAK file"); + } + readHeader(); + readDirectory(); +} + +PakFile::~PakFile() { + if (file.is_open()) { + file.close(); + } +} + +std::vector PakFile::getFileNames() { + std::vector names; + for (const auto& entry : entries) { + names.push_back(entry.name); + } + return names; +} + +int PakFile::getFileSize(const std::string& filename) { + for (const auto& entry : entries) { + if (filename == entry.name) { + return entry.length; + } + } + return -1; +} + +void PakFile::getFile(const std::string& filename, uint8_t* buffer) { + for (const auto& entry : entries) { + if (filename == entry.name) { + file.seekg(entry.offset, std::ios::beg); + file.read(reinterpret_cast(buffer), entry.length); + return; + } + } +} + +void PakFile::readHeader() { + file.read(reinterpret_cast(&header), sizeof(header)); + if (std::string(header.id, 4) != "PACK") { + throw std::runtime_error("Invalid PAK file"); + } +} + +void PakFile::readDirectory() { + file.seekg(header.dirOffset, std::ios::beg); + int numEntries = header.dirLength / sizeof(PakEntry); + entries.resize(numEntries); + file.read(reinterpret_cast(entries.data()), header.dirLength); +} \ No newline at end of file diff --git a/PakFS/PakFile.hpp b/PakFS/PakFile.hpp new file mode 100644 index 0000000..22fb229 --- /dev/null +++ b/PakFS/PakFile.hpp @@ -0,0 +1,41 @@ +#ifndef PAKFILE_HPP +#define PAKFILE_HPP + +#include +#include +#include +#include +#include + +class PakFile { +public: + PakFile(const std::string& filename); + ~PakFile(); + + std::vector getFileNames(); + + int getFileSize(const std::string& filename); + void getFile(const std::string& filename, uint8_t* buffer); + +private: + struct PakHeader { + char id[4]; + uint32_t dirOffset; + uint32_t dirLength; + }; + + struct PakEntry { + char name[56]; + uint32_t offset; + uint32_t length; + }; + + std::ifstream file; + PakHeader header; + std::vector entries; + + void readHeader(); + void readDirectory(); +}; + +#endif // PAKFILE_HPP \ No newline at end of file diff --git a/README.md b/README.md index 70dedc3..133b063 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# private-libpakfs \ No newline at end of file +# PakFS + +A simple C++ class for handling Quake2 PAK files from a base folder (and optionally including non-PAKed files). Two examples are included, along with an example base folder with bare files, a base PAK (pak0) and a PAK file with some overrides (pak2). The file pak1 intentionally doesn't exist. + +## example1.cpp + +Reads a single text file as a string from PAK files only. + +## example2.cpp + +Reads a selection of files from PAK files and the base directory, storing them in a char array (as you would for a binary file). diff --git a/base/folder2/subfolder1/testfile5 b/base/folder2/subfolder1/testfile5 new file mode 100644 index 0000000..d4acf68 --- /dev/null +++ b/base/folder2/subfolder1/testfile5 @@ -0,0 +1 @@ +this only exists within subfolders under base diff --git a/base/folder2/testfile4 b/base/folder2/testfile4 new file mode 100644 index 0000000..7d1e458 --- /dev/null +++ b/base/folder2/testfile4 @@ -0,0 +1 @@ +this file only exists in the base folder diff --git a/base/pak0.pak b/base/pak0.pak new file mode 100644 index 0000000000000000000000000000000000000000..cbe767f72c722a654de5f3f532021a5d7a665d2e GIT binary patch literal 337 zcmWG=boS<9U|=`^#3iZ4C25&CsX!)4;0#c;IuOJ38#cN7U{!` zF(%d+MMee&1EAuNjLc#MAWBpK8KD63ik^aJi9&LIUP)qRUNKO(qC}6Y1feV=vnU0u zPN5_tu|y$1FQ-x=wIZ{)1ZYm4LP27-0T +#include + +#include "PakFS/PakFS.hpp" + +#define PAKFS_BASE "base" + +int main() { + try { + PakFS pakfs(PAKFS_BASE); + + std::string filename = "testfile"; + + int filesize = pakfs.getFileSize(filename); + + if(filesize >= 0) + { + std::cout << "Size: " << filesize << std::endl << "----" << std::endl; + std::cout << pakfs.getFileString(filename); + }else{ + std::cout << "File not found: " << filename << std::endl << std::endl; + } + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 0; +} diff --git a/example2.cpp b/example2.cpp new file mode 100644 index 0000000..a08d189 --- /dev/null +++ b/example2.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "PakFS/PakFS.hpp" + +#define PAKFS_BASE "base" +#define PAKFS_PAKS_ONLY false + +int main() { + try { + PakFS pakfs(PAKFS_BASE, PAKFS_PAKS_ONLY); + + pakfs.listFiles(); + + std::vector filenames{ "testfile", "testfile2", "folder/testfile3" , "folder2/testfile4", "folder2/subfolder1/testfile5" }; + + for (const auto& filename : filenames) { + int filesize = pakfs.getFileSize(filename); + if(filesize >= 0) + { + std::cout << "File found: " << filename << " Size: " << filesize << std::endl << "----" << std::endl; + uint8_t* ByteArray; + ByteArray = new uint8_t[filesize + 1]; + pakfs.getFile(filename, ByteArray); + ByteArray[filesize] = 0; + std::cout << ByteArray << std::endl << "----"<< std::endl; + delete [] ByteArray; + }else{ + std::cout << "File not found: " << filename << std::endl << std::endl; + } + } + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 0; +}