10#include <BufferStream.h>
11#include <FileStream.h>
19constexpr std::string_view TAB_FILEPATH_LIST_STRIP_PATH_INDEX =
"projects/justcause/data/";
21[[nodiscard]] std::filesystem::path getArchivePath(
const TAB& tab, uint32_t archiveIndex) {
29 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
40 if (!std::filesystem::exists(path)) {
45 auto* tab =
new TAB{path};
46 auto packFile = std::unique_ptr<PackFile>(tab);
54 BufferStream::swap_endian(&
version);
64 std::vector<uint32_t> archiveAlignments(tab->
numArchives);
65 uint32_t alignment = 0;
67 alignment += (
static_cast<uint32_t
>(std::filesystem::file_size(::getArchivePath(*tab, i))) + tab->
sectorSize - 1) / tab->
sectorSize;
68 archiveAlignments[i] = alignment;
72 std::unordered_map<uint32_t, std::string> crackedHashes;
73 if (
const std::filesystem::path mapPath{std::filesystem::path{tab->
fullFilePath}.parent_path() / std::format(
"{}list.txt", tab->
getFilestem())}; std::filesystem::exists(mapPath)) {
74 std::ifstream mapFile{mapPath};
76 while (std::getline(mapFile, filepath)) {
77 if (filepath.empty() || filepath.starts_with(
':')) {
83 if (
const auto index = filepath.rfind(TAB_FILEPATH_LIST_STRIP_PATH_INDEX); index != std::string::npos) {
84 filepath = filepath.substr(index + TAB_FILEPATH_LIST_STRIP_PATH_INDEX.size());
90 const auto fileCount = (std::filesystem::file_size(tab->
fullFilePath) -
sizeof(uint32_t) * 3) / (
sizeof(uint32_t) * 3);
91 for (uint32_t i = 0; i < fileCount; i++) {
94 std::string entryPath;
97 entry.
crc32 = reader.read<uint32_t>();
98 if (crackedHashes.contains(entry.
crc32)) {
104 entry.
offset = reader.read<uint32_t>();
105 entry.
length = reader.read<uint32_t>();
109 if (entry.
offset < archiveAlignments[j]) {
120 tab->
entries.emplace(entryPath, entry);
123 callback(entryPath, entry);
130std::optional<std::vector<std::byte>>
TAB::readEntry(
const std::string& path_)
const {
132 const auto entry = this->
findEntry(path);
136 if (entry->unbaked) {
141 FileStream stream{::getArchivePath(*
this, entry->archiveIndex)};
145 stream.seek_in_u(entry->offset);
146 return stream.read_bytes(entry->length);
152 entry.
length = buffer.size();
162 const std::string outputPath = outputDir +
'/' + this->
getFilename();
165 std::vector<std::pair<std::string, Entry*>> entriesToBake;
167 entriesToBake.emplace_back(path, &entry);
169 std::ranges::sort(entriesToBake, [](
const std::pair<std::string, Entry*>& lhs,
const std::pair<std::string, Entry*>& rhs) {
170 return lhs.second->crc32 < rhs.second->crc32;
174 const std::filesystem::path mapPath{std::filesystem::path{outputDir} / std::format(
"{}list.txt", this->
getFilestem())};
175 if (!std::filesystem::exists(mapPath)) {
178 std::ofstream mapStream{mapPath};
181 FileStream outDir{outputPath, FileStream::OPT_READ | FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
187 outDir.pad<uint32_t>(3);
190 int32_t currentArchiveIndex = -1;
191 uint64_t currentArchiveLength = 0;
192 std::vector<uint32_t> archiveAlignments;
193 uint32_t totalArchiveSectors = 0;
194 const auto getNewArchivePath = [
this, &outputDir](uint32_t archiveIndex,
bool srcPath =
true) {
195 return std::filesystem::path{outputDir} / std::format(
"{}{}{}{}", this->
getFilestem(), archiveIndex,
ARC_EXTENSION, srcPath ?
".new" :
"");
197 const auto getNewArchiveStream = [
this, ¤tArchiveIndex, ¤tArchiveLength, &archiveAlignments, &totalArchiveSectors, &getNewArchivePath] {
198 if (currentArchiveIndex >= 0) {
200 archiveAlignments.push_back(totalArchiveSectors);
202 currentArchiveLength = 0;
204 auto out = FileStream{getNewArchivePath(++currentArchiveIndex), FileStream::OPT_READ | FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
209 FileStream currentArchive = getNewArchiveStream();
212 for (
auto& [path, entry] : entriesToBake) {
214 mapStream << path <<
'\n';
217 if (
const auto data = this->
readEntry(path)) {
219 uint64_t entryTotalSize = data->size() + padLength;
221 currentArchive = getNewArchiveStream();
225 entry->
offset = currentArchive.tell_out();
229 .pad<
char>(padLength,
'P');
232 .write<uint32_t>(entry->
crc32)
233 .write<uint32_t>(currentArchiveIndex == 0
235 : archiveAlignments[currentArchiveIndex - 1] + currentArchiveLength / this->
sectorSize)
236 .write<uint32_t>(entry->
length);
238 currentArchiveLength += entryTotalSize;
246 callback(path, *entry);
260 const auto srcPath = getNewArchivePath(i);
261 const auto destPath = getNewArchivePath(i,
false);
262 if (std::filesystem::exists(destPath)) {
263 std::filesystem::remove(destPath);
265 std::filesystem::rename(srcPath, destPath);
280TAB::operator std::string()
const {
281 return PackFile::operator std::string() + std::format(
" | {}", this->
version ==
Version::JC1_LE ?
"JC1 LE" :
"JC1 BE");
301 auto cleanPath = filepath;
304 cleanPath =
string::trim(std::filesystem::path{cleanPath}.filename().
string());
306 std::vector<std::byte> buffer;
309 std::memcpy(buffer.data(), cleanPath.c_str(), cleanPath.size());
311 buffer.push_back(std::byte{0x80});
312 if (buffer.size() % 64 < 56) {
313 buffer.resize(buffer.size() + (56 - buffer.size() % 64));
314 }
else if (buffer.size() % 64 > 56) {
315 buffer.resize(buffer.size() + (64 - buffer.size() % 64) + 56);
317 for (
int i = 7; i >= 0; i--) {
318 buffer.push_back(
static_cast<std::byte
>(
static_cast<uint64_t
>(
TAB_FILENAME_MAX_SIZE) * 8 >> i * 8 & 0xff));
321 for (std::span bufferU32{
reinterpret_cast<uint32_t*
>(buffer.data()), buffer.size() /
sizeof(uint32_t)};
auto& uint : bufferU32) {
322 BufferStream::swap_endian(&uint);
326 sha1_process(&sha1,
reinterpret_cast<const unsigned char*
>(buffer.data()), buffer.size());
327 BufferStream::swap_endian(&sha1.sha1.state[0]);
328 return sha1.sha1.state[0];
This class represents the metadata that a file has inside a PackFile.
uint64_t offset
Offset, format-specific meaning - 0 if unused, or if the offset genuinely is 0.
uint32_t archiveIndex
Which external archive this entry is in.
uint32_t crc32
CRC32 checksum - 0 if unused.
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length).
EntryCallbackBase< void > EntryCallback
void mergeUnbakedEntries()
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
std::string getFilestem() const
/home/user/pak01_dir.vpk -> pak01_dir
bool bake()
If output folder is an empty string, it will overwrite the original.
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
std::string getBakeOutputDir(const std::string &outputDir) const
void setFullFilePath(const std::string &outputDir)
std::string cleanEntryPath(const std::string &path) const
static Entry createNewEntry()
std::string_view getFilepath() const
/home/user/pak01_dir.vpk
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a TAB file.
void setVersion(Version version_)
uint32_t getSectorSize() const
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
void setSectorSize(uint32_t sectorSize_)
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
static uint32_t hashFilePath(const std::string &filepath)
Version getVersion() const
static std::unique_ptr< PackFile > create(const std::string &path, Version version=Version::JC1_LE, uint32_t sectorSize=2048)
Create a TAB file.
bool writeFileText(const std::filesystem::path &filepath, std::string_view text)
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
void trim(std::string &s)
void toLower(std::string &input)
std::string encodeHex(std::span< const std::byte > hex)
constexpr std::string_view ARC_EXTENSION
constexpr uint32_t ARC_CHUNK_SIZE
Chunk size in bytes (1gb).
constexpr auto TAB_FILENAME_MAX_SIZE
constexpr std::string_view TAB_HASHED_FILEPATH_PREFIX