diff --git a/README.md b/README.md
index cb0d4fdbb..a69b98663 100644
--- a/README.md
+++ b/README.md
@@ -188,11 +188,11 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
|
VPK pre-v1, v1-2, v54
diff --git a/include/sourcepp/crypto/SHA1.h b/include/sourcepp/crypto/SHA1.h
new file mode 100644
index 000000000..70b11ee3b
--- /dev/null
+++ b/include/sourcepp/crypto/SHA1.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace sourcepp::crypto {
+
+std::array computeSHA1(std::span buffer);
+
+} // namespace sourcepp::crypto
diff --git a/include/vpkpp/format/TAB.h b/include/vpkpp/format/TAB.h
new file mode 100644
index 000000000..926e22b3b
--- /dev/null
+++ b/include/vpkpp/format/TAB.h
@@ -0,0 +1,45 @@
+// ReSharper disable CppRedundantQualifier
+
+#pragma once
+
+#include
+
+#include "../PackFile.h"
+
+namespace vpkpp {
+
+constexpr std::string_view TAB_EXTENSION = ".tab";
+
+constexpr auto TAB_FILENAME_MAX_SIZE = 128;
+constexpr std::string_view TAB_HASHED_FILEPATH_PREFIX = "__hashed__/";
+
+constexpr std::string_view ARC_EXTENSION = ".arc";
+
+/// Chunk size in bytes (1gb)
+constexpr uint32_t ARC_CHUNK_SIZE = 1024 * 1024 * 1024;
+
+class TAB : public PackFileReadOnly {
+public:
+ /// Open a TAB file
+ [[nodiscard]] static std::unique_ptr open(const std::string& path, const EntryCallback& callback = nullptr);
+
+ [[nodiscard]] std::optional> readEntry(const std::string& path_) const override;
+
+ [[nodiscard]] Attribute getSupportedEntryAttributes() const override;
+
+ [[nodiscard]] explicit operator std::string() const override;
+
+ [[nodiscard]] static uint32_t hashFilePath(const std::string& filepath);
+
+protected:
+ using PackFileReadOnly::PackFileReadOnly;
+
+ uint32_t version = 0;
+ uint32_t sectorSize = 0;
+ uint32_t numArchives = 0;
+
+private:
+ VPKPP_REGISTER_PACKFILE_OPEN(TAB_EXTENSION, &TAB::open);
+};
+
+} // namespace vpkpp
diff --git a/include/vpkpp/vpkpp.h b/include/vpkpp/vpkpp.h
index 7fe67272a..39d5c005c 100644
--- a/include/vpkpp/vpkpp.h
+++ b/include/vpkpp/vpkpp.h
@@ -16,6 +16,7 @@
#include "format/ORE.h"
#include "format/PAK.h"
#include "format/PCK.h"
+#include "format/TAB.h"
#include "format/VPK.h"
#include "format/VPK_VTMB.h"
#include "format/VPP.h"
diff --git a/lang/c/include/vpkppc/format/TAB.h b/lang/c/include/vpkppc/format/TAB.h
new file mode 100644
index 000000000..401c9be19
--- /dev/null
+++ b/lang/c/include/vpkppc/format/TAB.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../PackFile.h"
+
+VPKPP_EXTERNVAR const char* VPKPP_TAB_EXTENSION;
+
+VPKPP_EXTERNVAR const int VPKPP_TAB_FILENAME_MAX_SIZE;
+VPKPP_EXTERNVAR const char* VPKPP_TAB_HASHED_FILEPATH_PREFIX;
+
+VPKPP_EXTERNVAR const char* VPKPP_ARC_EXTENSION;
+VPKPP_EXTERNVAR const uint32_t VPKPP_ARC_CHUNK_SIZE;
+
+VPKPP_API vpkpp_pack_file_handle_t vpkpp_tab_open(const char* path, vpkpp_entry_callback_t callback); // REQUIRES MANUAL FREE: vpkpp_close
+VPKPP_API uint32_t vpkpp_tab_hash_file_path(const char* filepath);
+
+// C++ conversion routines
+#ifdef __cplusplus
+
+#include
+
+#endif
diff --git a/lang/c/include/vpkppc/vpkpp.h b/lang/c/include/vpkppc/vpkpp.h
index 80c2efddc..25854e8c7 100644
--- a/lang/c/include/vpkppc/vpkpp.h
+++ b/lang/c/include/vpkppc/vpkpp.h
@@ -16,6 +16,7 @@
#include "format/ORE.h"
#include "format/PAK.h"
#include "format/PCK.h"
+#include "format/TAB.h"
#include "format/VPK.h"
#include "format/VPK_VTMB.h"
#include "format/WAD3.h"
diff --git a/lang/c/src/vpkppc/_vpkppc.cmake b/lang/c/src/vpkppc/_vpkppc.cmake
index a3a54d5ed..76b2edf42 100644
--- a/lang/c/src/vpkppc/_vpkppc.cmake
+++ b/lang/c/src/vpkppc/_vpkppc.cmake
@@ -11,6 +11,7 @@ add_pretty_parser(vpkpp C
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/ORE.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/PAK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/PCK.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/TAB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/VPK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/VPK_VTMB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/VPP.h"
@@ -34,6 +35,7 @@ add_pretty_parser(vpkpp C
"${CMAKE_CURRENT_LIST_DIR}/format/ORE.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PAK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PCK.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/format/TAB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPP.cpp"
diff --git a/lang/c/src/vpkppc/format/TAB.cpp b/lang/c/src/vpkppc/format/TAB.cpp
new file mode 100644
index 000000000..ebb5c1d73
--- /dev/null
+++ b/lang/c/src/vpkppc/format/TAB.cpp
@@ -0,0 +1,30 @@
+#include
+
+#include
+
+using namespace sourceppc;
+using namespace vpkpp;
+
+const char* VPKPP_TAB_EXTENSION = TAB_EXTENSION.data();
+
+const int VPKPP_TAB_FILENAME_MAX_SIZE = TAB_FILENAME_MAX_SIZE;
+const char* VPKPP_TAB_HASHED_FILEPATH_PREFIX = TAB_HASHED_FILEPATH_PREFIX.data();
+
+const char* VPKPP_ARC_EXTENSION = ARC_EXTENSION.data();
+const uint32_t VPKPP_ARC_CHUNK_SIZE = ARC_CHUNK_SIZE;
+
+VPKPP_API vpkpp_pack_file_handle_t vpkpp_tab_open(const char* path, vpkpp_entry_callback_t callback) {
+ SOURCEPP_EARLY_RETURN_VAL(path, nullptr);
+
+ auto packFile = TAB::open(path, callback ? [callback](const std::string& entryPath, const Entry& entry) {
+ callback(entryPath.c_str(), const_cast(&entry));
+ } : static_cast(nullptr));
+ if (!packFile) {
+ return nullptr;
+ }
+ return packFile.release();
+}
+
+VPKPP_API uint32_t vpkpp_tab_hash_file_path(const char* filepath) {
+ return TAB::hashFilePath(filepath);
+}
diff --git a/lang/csharp/src/vpkpp/DLL.cs b/lang/csharp/src/vpkpp/DLL.cs
index 9ad6159b2..f09f66c76 100644
--- a/lang/csharp/src/vpkpp/DLL.cs
+++ b/lang/csharp/src/vpkpp/DLL.cs
@@ -94,10 +94,16 @@ internal static partial class DLL
[LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
public static partial nint vpkpp_pck_open(string path, EntryCallbackNative? callback);
-
+
+ [LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
+ public static partial nint vpkpp_tab_open(string path, EntryCallbackNative? callback);
+
+ [LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
+ public static partial uint vpkpp_tab_hash_file_path(string path);
+
[LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
public static partial nint vpkpp_vpk_create(string path);
-
+
[LibraryImport(Name, StringMarshalling = StringMarshalling.Utf8)]
public static partial nint vpkpp_vpk_create_with_options(string path, uint version);
diff --git a/lang/csharp/src/vpkpp/Format/TAB.cs b/lang/csharp/src/vpkpp/Format/TAB.cs
new file mode 100644
index 000000000..9d7fd3eac
--- /dev/null
+++ b/lang/csharp/src/vpkpp/Format/TAB.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace sourcepp.vpkpp.Format;
+
+using OpenPropertyRequest = Func;
+
+using EntryCallback = Action;
+
+public class TAB : PackFile
+{
+ protected TAB(nint handle, bool managed = true) : base(handle, managed)
+ {
+ }
+
+ public new static TAB? Open(string path, EntryCallback? callback = null, OpenPropertyRequest? _ = null)
+ {
+ var handle = DLL.vpkpp_tab_open(path, callback is not null ? (entryPath, entry) =>
+ {
+ callback(entryPath, new Entry(entry, false));
+ } : null);
+ return handle == nint.Zero ? null : new TAB(handle);
+ }
+
+ public static uint HashFilepath(string path)
+ {
+ return DLL.vpkpp_tab_hash_file_path(path);
+ }
+}
diff --git a/lang/python/src/vpkpp.h b/lang/python/src/vpkpp.h
index 79c3a4f01..15eb3f927 100644
--- a/lang/python/src/vpkpp.h
+++ b/lang/python/src/vpkpp.h
@@ -255,6 +255,16 @@ inline void register_python(py::module_& m) {
.def("get_godot_version", &PCK::getGodotVersion)
.def("set_godot_version", &PCK::setGodotVersion, "major"_a = 0, "minor"_a = 0, "patch"_a = 0);
+ vpkpp.attr("TAB_EXTENSION") = TAB_EXTENSION;
+ vpkpp.attr("TAB_FILENAME_MAX_SIZE") = TAB_FILENAME_MAX_SIZE;
+ vpkpp.attr("TAB_HASHED_FILEPATH_PREFIX") = TAB_HASHED_FILEPATH_PREFIX;
+ vpkpp.attr("ARC_EXTENSION") = ARC_EXTENSION;
+ vpkpp.attr("ARC_CHUNK_SIZE") = ARC_CHUNK_SIZE;
+
+ py::class_(vpkpp, "TAB")
+ .def_static("open", &TAB::open, "path"_a, "callback"_a = nullptr)
+ .def_static("hash_filepath", &FGP::hashFilePath);
+
vpkpp.attr("VPK_SIGNATURE") = VPK_SIGNATURE;
vpkpp.attr("VPK_DIR_INDEX") = VPK_DIR_INDEX;
vpkpp.attr("VPK_ENTRY_TERM") = VPK_ENTRY_TERM;
diff --git a/src/sourcepp/String.cpp b/src/sourcepp/String.cpp
index 884c6ff15..34cd56bfa 100644
--- a/src/sourcepp/String.cpp
+++ b/src/sourcepp/String.cpp
@@ -20,7 +20,7 @@ std::mt19937& getRandomGenerator() {
using namespace sourcepp;
bool string::contains(std::string_view s, char c) {
- return std::find(s.begin(), s.end(), c) != s.end();
+ return std::ranges::find(s, c) != s.end();
}
bool string::matches(std::string_view in, std::string_view search) {
@@ -67,7 +67,7 @@ bool string::iequals(std::string_view s1, std::string_view s2) {
// https://stackoverflow.com/a/217605
void string::ltrim(std::string& s) {
- s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char c) { return !std::isspace(c); }));
+ s.erase(s.begin(), std::ranges::find_if(s, [](char c) { return !std::isspace(c); }));
}
std::string_view string::ltrim(std::string_view s) {
@@ -108,7 +108,7 @@ std::string string::trimInternal(std::string_view s) {
}
void string::ltrim(std::string& s, std::string_view chars) {
- s.erase(s.begin(), std::find_if(s.begin(), s.end(), [chars](char c) {
+ s.erase(s.begin(), std::ranges::find_if(s, [chars](char c) {
return !contains(chars, c);
}));
}
@@ -165,7 +165,7 @@ std::vector string::split(std::string_view s, char delim) {
}
void string::toLower(std::string& input) {
- std::transform(input.begin(), input.end(), input.begin(), [](unsigned char c){ return std::tolower(c); });
+ std::ranges::transform(input, input.begin(), [](unsigned char c){ return std::tolower(c); });
}
std::string string::toLower(std::string_view input) {
@@ -175,7 +175,7 @@ std::string string::toLower(std::string_view input) {
}
void string::toUpper(std::string& input) {
- std::transform(input.begin(), input.end(), input.begin(), [](unsigned char c){ return std::toupper(c); });
+ std::ranges::transform(input, input.begin(), [](unsigned char c){ return std::toupper(c); });
}
std::string string::toUpper(std::string_view input) {
@@ -235,7 +235,7 @@ void string::normalizeSlashes(std::string& path, bool stripSlashPrefix, bool str
}
void string::denormalizeSlashes(std::string& path, bool stripSlashPrefix, bool stripSlashSuffix) {
- std::replace(path.begin(), path.end(), '/', '\\');
+ std::ranges::replace(path, '/', '\\');
if (stripSlashPrefix && path.starts_with('\\')) {
path = path.substr(1);
}
diff --git a/src/sourcepp/crypto/SHA1.cpp b/src/sourcepp/crypto/SHA1.cpp
new file mode 100644
index 000000000..ca0132749
--- /dev/null
+++ b/src/sourcepp/crypto/SHA1.cpp
@@ -0,0 +1,21 @@
+#include
+
+#include
+
+#include
+
+using namespace sourcepp;
+
+std::array crypto::computeSHA1(std::span buffer) {
+ if (!LTM_MATH || buffer.empty()) {
+ return {};
+ }
+
+ hash_state sha1;
+ sha1_init(&sha1);
+ sha1_process(&sha1, reinterpret_cast(buffer.data()), buffer.size());
+
+ std::array final{};
+ sha1_done(&sha1, reinterpret_cast(final.data()));
+ return final;
+}
diff --git a/src/sourcepp/crypto/_crypto.cmake b/src/sourcepp/crypto/_crypto.cmake
index 1b6714cae..e7c8988dd 100644
--- a/src/sourcepp/crypto/_crypto.cmake
+++ b/src/sourcepp/crypto/_crypto.cmake
@@ -5,6 +5,7 @@ list(APPEND ${PROJECT_NAME}_crypto_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/Globals.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/MD5.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/RSA.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/SHA1.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/SHA256.h")
add_library(${PROJECT_NAME}_crypto STATIC
@@ -15,6 +16,7 @@ add_library(${PROJECT_NAME}_crypto STATIC
"${CMAKE_CURRENT_LIST_DIR}/Globals.cpp"
"${CMAKE_CURRENT_LIST_DIR}/MD5.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RSA.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/SHA1.cpp"
"${CMAKE_CURRENT_LIST_DIR}/SHA256.cpp")
target_precompile_headers(${PROJECT_NAME}_crypto PUBLIC ${${PROJECT_NAME}_crypto_HEADERS})
diff --git a/src/vpkpp/_vpkpp.cmake b/src/vpkpp/_vpkpp.cmake
index 60d305aff..ac0597249 100644
--- a/src/vpkpp/_vpkpp.cmake
+++ b/src/vpkpp/_vpkpp.cmake
@@ -13,6 +13,7 @@ add_pretty_parser(vpkpp
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/ORE.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PAK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PCK.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/TAB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK_VTMB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPP.h"
@@ -36,6 +37,7 @@ add_pretty_parser(vpkpp
"${CMAKE_CURRENT_LIST_DIR}/format/ORE.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PAK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PCK.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/format/TAB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPP.cpp"
diff --git a/src/vpkpp/format/TAB.cpp b/src/vpkpp/format/TAB.cpp
new file mode 100644
index 000000000..053763153
--- /dev/null
+++ b/src/vpkpp/format/TAB.cpp
@@ -0,0 +1,177 @@
+// ReSharper disable CppParameterMayBeConst
+// ReSharper disable CppRedundantQualifier
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace sourcepp;
+using namespace vpkpp;
+
+namespace {
+
+constexpr std::string_view TAB_FILEPATH_LIST_STRIP_PATH_INDEX = "projects/justcause/data/";
+
+[[nodiscard]] std::filesystem::path getArchivePath(const TAB& tab, uint32_t archiveIndex) {
+ return std::filesystem::path{tab.getFilepath()}.parent_path() / std::format("{}{}{}", tab.getFilestem(), archiveIndex, ARC_EXTENSION);
+}
+
+} // namespace
+
+std::unique_ptr TAB::open(const std::string& path, const EntryCallback& callback) {
+ if (!std::filesystem::exists(path)) {
+ // File does not exist
+ return nullptr;
+ }
+
+ auto* tab = new TAB{path};
+ auto packFile = std::unique_ptr(tab);
+
+ FileStream reader{tab->fullFilePath};
+ reader.seek_in(0);
+
+ reader >> tab->version;
+ if (tab->version != 3) {
+ BufferStream::swap_endian(&tab->version);
+ if (tab->version == 3) {
+ reader.set_big_endian(true);
+ } else {
+ return nullptr;
+ }
+ }
+
+ reader >> tab->sectorSize >> tab->numArchives;
+
+ std::vector> archives;
+ uint32_t alignment = 0;
+ for (uint32_t i = 0; i < tab->numArchives; i++) {
+ auto& [archivePath, archiveSize, archiveAlignment] = archives.emplace_back();
+ archivePath = ::getArchivePath(*tab, i),
+ archiveSize = static_cast(std::filesystem::file_size(archivePath));
+ alignment += (archiveSize + tab->sectorSize - 1) / tab->sectorSize;
+ archiveAlignment = alignment;
+ }
+
+ // Here we load in the filepath list if it exists
+ std::unordered_map crackedHashes;
+ if (const std::filesystem::path mapPath{std::filesystem::path{tab->fullFilePath}.parent_path() / std::format("{}list.txt", tab->getFilestem())}; std::filesystem::exists(mapPath)) {
+ std::ifstream mapFile{mapPath};
+ std::string filepath;
+ while (std::getline(mapFile, filepath)) {
+ if (filepath.empty() || filepath.starts_with(':')) {
+ continue;
+ }
+ string::trim(filepath);
+ string::normalizeSlashes(filepath);
+ string::toLower(filepath);
+ if (const auto index = filepath.rfind(TAB_FILEPATH_LIST_STRIP_PATH_INDEX); index != std::string::npos) {
+ filepath = filepath.substr(index + TAB_FILEPATH_LIST_STRIP_PATH_INDEX.size());
+ }
+ crackedHashes[TAB::hashFilePath(filepath)] = filepath;
+ }
+ }
+
+ const auto fileCount = (std::filesystem::file_size(tab->fullFilePath) - sizeof(uint32_t) * 3) / (sizeof(uint32_t) * 3);
+ for (uint32_t i = 0; i < fileCount; i++) {
+ Entry entry = createNewEntry();
+
+ std::string entryPath;
+
+ // note: NOT a CRC32! check TAB::hashFilePath
+ entry.crc32 = reader.read();
+ if (crackedHashes.contains(entry.crc32)) {
+ entryPath = tab->cleanEntryPath(crackedHashes[entry.crc32]);
+ } else {
+ entryPath = tab->cleanEntryPath(TAB_HASHED_FILEPATH_PREFIX.data() + string::encodeHex({reinterpret_cast(&entry.crc32), sizeof(entry.crc32)}));
+ }
+
+ entry.offset = reader.read();
+ entry.length = reader.read();
+ entry.archiveIndex = 0;
+
+ for (int j = 0; j < archives.size(); j++) {
+ if (entry.offset < std::get<2>(archives[j])) {
+ entry.archiveIndex = j;
+ break;
+ }
+ }
+ if (entry.archiveIndex == 0) {
+ entry.offset = (entry.offset * tab->sectorSize) % ARC_CHUNK_SIZE;
+ } else {
+ entry.offset = (entry.offset - std::get<2>(archives[entry.archiveIndex - 1])) * tab->sectorSize % ARC_CHUNK_SIZE;
+ }
+
+ tab->entries.emplace(entryPath, entry);
+
+ if (callback) {
+ callback(entryPath, entry);
+ }
+ }
+
+ return packFile;
+}
+
+std::optional> TAB::readEntry(const std::string& path_) const {
+ const auto path = this->cleanEntryPath(path_);
+ const auto entry = this->findEntry(path);
+ if (!entry) {
+ return std::nullopt;
+ }
+ if (entry->unbaked) {
+ return readUnbakedEntry(*entry);
+ }
+
+ // It's baked into the file on disk
+ FileStream stream{::getArchivePath(*this, entry->archiveIndex)};
+ if (!stream) {
+ return std::nullopt;
+ }
+ stream.seek_in_u(entry->offset);
+ return stream.read_bytes(entry->length);
+}
+
+Attribute TAB::getSupportedEntryAttributes() const {
+ using enum Attribute;
+ return ARCHIVE_INDEX | LENGTH;
+}
+
+TAB::operator std::string() const {
+ return PackFileReadOnly::operator std::string() + std::format(" | Version v{}", this->version);
+}
+
+uint32_t TAB::hashFilePath(const std::string& filepath) {
+ auto cleanPath = filepath;
+ string::normalizeSlashes(cleanPath);
+ string::toLower(cleanPath);
+ cleanPath = string::trim(std::filesystem::path{cleanPath}.filename().string());
+
+ std::vector buffer;
+ buffer.resize(TAB_FILENAME_MAX_SIZE);
+ std::memset(buffer.data(), 0, TAB_FILENAME_MAX_SIZE);
+ std::memcpy(buffer.data(), cleanPath.c_str(), cleanPath.size());
+
+ buffer.push_back(std::byte{0x80});
+ if (buffer.size() % 64 < 56) {
+ buffer.resize(buffer.size() + (56 - buffer.size() % 64));
+ } else if (buffer.size() % 64 > 56) {
+ buffer.resize(buffer.size() + (64 - buffer.size() % 64) + 56);
+ }
+ for (int i = 7; i >= 0; i--) {
+ buffer.push_back(static_cast(static_cast(TAB_FILENAME_MAX_SIZE) * 8 >> i * 8 & 0xff));
+ }
+
+ for (std::span bufferU32{reinterpret_cast(buffer.data()), buffer.size() / sizeof(uint32_t)}; auto& uint : bufferU32) {
+ BufferStream::swap_endian(&uint);
+ }
+ hash_state sha1;
+ sha1_init(&sha1);
+ sha1_process(&sha1, reinterpret_cast(buffer.data()), buffer.size());
+ BufferStream::swap_endian(&sha1.sha1.state[0]);
+ return sha1.sha1.state[0];
+}
|