-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(vpkpp): add example pack file implementation
- Loading branch information
1 parent
28bbdbe
commit 48e8706
Showing
2 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
#include "SamplePackFileImpl.h" | ||
|
||
#include <filesystem> | ||
#include <tuple> | ||
|
||
#include <vpkedit/detail/Misc.h> | ||
|
||
using namespace vpkedit; | ||
using namespace vpkedit::detail; | ||
|
||
EXAMPLE::EXAMPLE(const std::string& fullFilePath_, PackFileOptions options_) | ||
: PackFile(fullFilePath_, options_) { | ||
// Add a new type in the PackFileType enum for this file type, and set it here | ||
this->type = PackFileType::UNKNOWN; | ||
} | ||
|
||
std::unique_ptr<PackFile> EXAMPLE::open(const std::string& path, PackFileOptions options, const Callback& callback) { | ||
// Check if the file exists | ||
if (!std::filesystem::exists(path)) { | ||
// File does not exist | ||
return nullptr; | ||
} | ||
|
||
// Create the pack file | ||
auto* example = new EXAMPLE{path, options}; | ||
auto packFile = std::unique_ptr<PackFile>(example); | ||
|
||
// Here is where you add entries to the entries member variable | ||
// It's a map between a directory and a vector of entries | ||
// Every time an entry is added, the callback should be called if the callback exists | ||
std::vector<std::pair<std::string, std::string>> samplePaths{ | ||
{"a/b/c", "skibidi_toilet.png"}, | ||
{"d/c", "boykisser.mdl"}, | ||
{"", "megamind.txt"}, | ||
}; | ||
for (auto& [dir, name] : samplePaths) { | ||
// The path needs to be normalized, and respect case sensitivity | ||
::normalizeSlashes(dir); | ||
if (!example->isCaseSensitive()) { | ||
::toLowerCase(dir); | ||
::toLowerCase(name); | ||
} | ||
|
||
// Create the list if it doesn't exist | ||
if (!example->entries.contains(dir)) { | ||
example->entries[dir] = {}; | ||
} | ||
|
||
// Use the createNewEntry function to avoid Entry having to friend every single damn class | ||
Entry entry = createNewEntry(); | ||
|
||
// The path should be the full path to the file | ||
entry.path = dir; | ||
entry.path += dir.empty() ? "" : "/"; | ||
entry.path += name; | ||
|
||
// We already did it at the start, but this is how it's usually done | ||
//::normalizeSlashes(entry.path); | ||
//if (!options.allowUppercaseLettersInFilenames) { | ||
// ::toLowerCase(entry.path); | ||
//} | ||
|
||
// The length should be the full uncompressed length of the file data in bytes | ||
entry.length = 42; | ||
|
||
// The compressed length will be non-zero if the file is compressed, the length is in bytes | ||
// This can be omitted if unused, 0 is the default | ||
entry.compressedLength = 0; | ||
|
||
// This is the CRC32 of the file - a helper function to compute it is in <vpkedit/detail/CRC.h> | ||
// This can also be omitted if unused, 0 is the default | ||
entry.crc32 = 0; | ||
|
||
// Add the entry to the entries map | ||
example->entries[dir].push_back(std::move(entry)); | ||
|
||
// Call the callback | ||
if (callback) { | ||
callback(dir, entry); | ||
} | ||
} | ||
|
||
return packFile; | ||
} | ||
|
||
std::optional<std::vector<std::byte>> EXAMPLE::readEntry(const Entry& entry) const { | ||
// Include this code verbatim - will likely be moved to a utility method soon | ||
if (entry.unbaked) { | ||
// Get the stored data | ||
for (const auto& [unbakedEntryDir, unbakedEntryList] : this->unbakedEntries) { | ||
for (const Entry& unbakedEntry : unbakedEntryList) { | ||
if (unbakedEntry.path == entry.path) { | ||
std::vector<std::byte> unbakedData; | ||
if (isEntryUnbakedUsingByteBuffer(unbakedEntry)) { | ||
unbakedData = std::get<std::vector<std::byte>>(getEntryUnbakedData(unbakedEntry)); | ||
} else { | ||
unbakedData = ::readFileData(std::get<std::string>(getEntryUnbakedData(unbakedEntry))); | ||
} | ||
return unbakedData; | ||
} | ||
} | ||
} | ||
return std::nullopt; | ||
} | ||
|
||
// Use the contents of the entry to access the file data and return it | ||
// Return std::nullopt if there was an error during any step of this process - not an empty buffer! | ||
return std::nullopt; | ||
} | ||
|
||
Entry& EXAMPLE::addEntryInternal(Entry& entry, const std::string& filename_, std::vector<std::byte>& buffer, EntryOptions options_) { | ||
// Include this verbatim | ||
auto filename = filename_; | ||
if (!this->isCaseSensitive()) { | ||
::toLowerCase(filename); | ||
} | ||
auto [dir, name] = ::splitFilenameAndParentDir(filename); | ||
|
||
// Initialize the entry - set the entry properties just like in EXAMPLE::open | ||
entry.path = filename; | ||
// ... | ||
|
||
// Include this verbatim | ||
if (!this->unbakedEntries.contains(dir)) { | ||
this->unbakedEntries[dir] = {}; | ||
} | ||
this->unbakedEntries.at(dir).push_back(entry); | ||
return this->unbakedEntries.at(dir).back(); | ||
} | ||
|
||
bool EXAMPLE::bake(const std::string& outputDir_, const PackFile::Callback& callback) { | ||
// Get the proper file output folder (include this verbatim) | ||
std::string outputDir = this->getBakeOutputDir(outputDir_); | ||
std::string outputPath = outputDir + '/' + this->getFilename(); | ||
|
||
// Loop over all entries and save them | ||
for (const auto& [entryDir, entries] : this->getBakedEntries()) { | ||
for (const Entry& entry : entries) { | ||
auto binData = this->readEntry(entry); | ||
if (!binData) { | ||
continue; | ||
} | ||
|
||
// Write data here | ||
// ... | ||
|
||
// Call the callback | ||
if (callback) { | ||
callback(entry.getParentPath(), entry); | ||
} | ||
} | ||
} | ||
// Yes this is copy-paste, you could probably turn this into a lambda and call it on both maps | ||
for (const auto& [entryDir, entries] : this->getUnbakedEntries()) { | ||
for (const Entry& entry : entries) { | ||
auto binData = this->readEntry(entry); | ||
if (!binData) { | ||
continue; | ||
} | ||
|
||
// Write data here | ||
// ... | ||
|
||
// Call the callback | ||
if (callback) { | ||
callback(entry.getParentPath(), entry); | ||
} | ||
} | ||
} | ||
|
||
// Call this when all the entries have been written to disk | ||
this->mergeUnbakedEntries(); | ||
|
||
// Include this verbatim at the end of the function | ||
PackFile::setFullFilePath(outputDir); | ||
// Return false before this if it encounters an error | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#pragma once | ||
|
||
#include <vpkedit/PackFile.h> | ||
|
||
/* | ||
* --- Example Pack File Implementation --- | ||
* | ||
* This code is a template for adding a new file format to libvpkedit. Copy these two files and follow the comments! | ||
* | ||
* Any methods marked as "[OPTIONAL]" can be deleted if the file format does not support them. | ||
* | ||
* Note that if you are writing a read-only parser, you will need to make the following deviations: | ||
* - Inherit from PackFileReadOnly instead of PackFile | ||
* - Don't implement the bake and addEntryInternal methods (marked with "[WRITE]") | ||
* | ||
* If these instructions are followed, you should see your format appear in the VPKEdit GUI automatically. | ||
*/ | ||
|
||
namespace vpkedit { | ||
|
||
// Define the accepted extension(s) as constant(s) | ||
constexpr std::string_view EXAMPLE_EXTENSION = ".example"; | ||
|
||
// All file formats need a static open method, and need to derive four methods at minimum from PackFile | ||
class EXAMPLE : public PackFile { | ||
public: | ||
// Always return a unique_ptr to PackFile so it has a uniform return type | ||
// If your type needs any new options, add them to PackFileOptions - it was the cleanest way to do it without messing with variants or std::any | ||
[[nodiscard]] static std::unique_ptr<PackFile> open(const std::string& path, PackFileOptions options = {}, const Callback& callback = nullptr); | ||
|
||
// [OPTIONAL] Implement this and return true if your file format is case-sensitive | ||
[[nodiscard]] constexpr bool isCaseSensitive() const noexcept override { | ||
return PackFile::isCaseSensitive(); | ||
} | ||
|
||
// Returns the raw data the Entry points to | ||
[[nodiscard]] std::optional<std::vector<std::byte>> readEntry(const Entry& entry) const override; | ||
|
||
// [WRITE] Save any changes made to the opened file(s) | ||
bool bake(const std::string& outputDir_ /*= ""*/, const Callback& callback /*= nullptr*/) override; | ||
|
||
// [OPTIONAL] Returns any attributes your file format's entries have (refer to the other file formats for more info) | ||
[[nodiscard]] std::vector<Attribute> getSupportedEntryAttributes() const override { | ||
return PackFile::getSupportedEntryAttributes(); | ||
} | ||
|
||
// [OPTIONAL] Add any custom file info here (refer to the other file formats for how to structure this) | ||
[[nodiscard]] explicit operator std::string() const override { | ||
return PackFile::operator std::string(); | ||
} | ||
|
||
protected: | ||
EXAMPLE(const std::string& fullFilePath_, PackFileOptions options_); | ||
|
||
// [WRITE] Adds a new entry from either a filename or a buffer | ||
// Again, if your type needs any new options specific to entries, add them to EntryOptions | ||
Entry& addEntryInternal(Entry& entry, const std::string& filename_, std::vector<std::byte>& buffer, EntryOptions options_) override; | ||
|
||
private: | ||
// Finally, register the open method with the extension | ||
// Remember since C++ is STUPID you need to add this header to PackFile.cpp as well, or this will get optimized away | ||
VPKEDIT_REGISTER_PACKFILE_OPEN(EXAMPLE_EXTENSION, &EXAMPLE::open); | ||
}; | ||
|
||
} // namespace vpkedit |