diff --git a/README.md b/README.md
index fc46a8b64..626b6f9d5 100644
--- a/README.md
+++ b/README.md
@@ -252,9 +252,10 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
PAK (Quake, WON Half-Life)
+ • Daikatana modifications
+ • HROT modifications
• SiN modifications
• SiN Reloaded modifications
- • HROT modifications
|
✅ |
✅ |
@@ -546,8 +547,8 @@ These are only the tools and games using `sourcepp` that I know of. If you would
- HOG parser was contributed by [@erysdren](https://github.com/erysdren).
- OL parser is based on [reverse-engineering work](https://github.com/erysdren/scratch/blob/main/kaitai/worldcraft_ol.ksy) by [@erysdren](https://github.com/erysdren).
- ORE parser is based on [reverse-engineering work](https://github.com/erysdren/narbacular-drop-tools) by [@erysdren](https://github.com/erysdren).
+ - PAK's Daikatana, SiN Reloaded support was contributed by [@erysdren](https://github.com/erysdren).
- REZ parser is based on [RezExtract](https://github.com/no-lith/RezExtract) by [@no-lith](https://github.com/no-lith).
- - SiN Reloaded parser was contributed by [@erysdren](https://github.com/erysdren).
- VPP parser was contributed by [@erysdren](https://github.com/erysdren).
- WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
- `vtfpp`
diff --git a/docs/index.html b/docs/index.html
index e770f182a..b3e6a272a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -664,9 +664,10 @@ Supported Formats
PAK (Quake, WON Half-Life)
+ - Daikatana modifications
+ - HROT modifications
- SiN modifications
- SiN Reloaded modifications
- - HROT modifications
|
✅ |
@@ -901,8 +902,8 @@ Special Thanks
HOG parser was contributed by @erysdren.
OL parser is based on reverse-engineering work by @erysdren.
ORE parser is based on reverse-engineering work by @erysdren.
+ PAK's Daikatana, SiN Reloaded support was contributed by @erysdren.
REZ parser is based on RezExtract by @no-lith.
- SiN Reloaded parser was contributed by @erysdren.
VPP parser was contributed by @erysdren.
WAD3 parser/writer was contributed by @ozxybox.
diff --git a/include/kvpp/KV1.h b/include/kvpp/KV1.h
index c69f2637d..2b73ce0eb 100644
--- a/include/kvpp/KV1.h
+++ b/include/kvpp/KV1.h
@@ -41,7 +41,7 @@ class KV1ElementReadable {
return static_cast(this->getValue());
} else if constexpr (std::same_as || std::same_as) {
V out = 0;
- if (this->value.length() == 10 && this->value.starts_with("0x") && sourcepp::parser::text::isNumber(this->value.substr(2))) {
+ if (this->value.length() == 10 && this->value.starts_with("0x") && sourcepp::string::isNumber(this->value.substr(2))) {
sourcepp::string::toInt(this->value.substr(2), out, 16);
} else {
sourcepp::string::toInt(this->value, out);
diff --git a/include/kvpp/KV1Writer.h b/include/kvpp/KV1Writer.h
index 0eb004f84..64278af70 100644
--- a/include/kvpp/KV1Writer.h
+++ b/include/kvpp/KV1Writer.h
@@ -36,7 +36,7 @@ class KV1ElementWritable {
return static_cast(this->getValue());
} else if constexpr (std::same_as || std::same_as) {
V out = 0;
- if (this->value.length() == 10 && this->value.starts_with("0x") && sourcepp::parser::text::isNumber(this->value.substr(2))) {
+ if (this->value.length() == 10 && this->value.starts_with("0x") && sourcepp::string::isNumber(this->value.substr(2))) {
sourcepp::string::toInt(this->value.substr(2), out, 16);
} else {
sourcepp::string::toInt(this->value, out);
diff --git a/include/sourcepp/String.h b/include/sourcepp/String.h
index 651050c03..febaae8bf 100644
--- a/include/sourcepp/String.h
+++ b/include/sourcepp/String.h
@@ -12,6 +12,62 @@
namespace sourcepp::string {
+/**
+ * If a char is a newline character.
+ * @param c The char.
+ * @return The char is a newline character.
+ */
+[[nodiscard]] bool isNewLine(char c);
+
+/**
+ * If a string is entirely composed of newline characters.
+ * @param str The string.
+ * @return The string is entirely composed of newline characters.
+ */
+[[nodiscard]] bool isNewLine(std::string_view str);
+
+/**
+ * If a char is a whitespace character.
+ * @param c The char.
+ * @return The char is a whitespace character.
+ */
+[[nodiscard]] bool isWhitespace(char c);
+
+/**
+ * If a string is entirely composed of whitespace characters.
+ * @param str The string.
+ * @return The string is entirely composed of whitespace characters.
+ */
+[[nodiscard]] bool isWhitespace(std::string_view str);
+
+/**
+ * If a char is a numerical character (0-9).
+ * @param c The char.
+ * @return The char is a numerical character.
+ */
+[[nodiscard]] bool isNumber(char c);
+
+/**
+ * If a string is entirely composed of numerical characters (0-9).
+ * @param str The string.
+ * @return The string is entirely composed of numerical characters.
+ */
+[[nodiscard]] bool isNumber(std::string_view str);
+
+/**
+ * If a char is a letter (a-z, A-Z) or a numerical character (0-9).
+ * @param c The char.
+ * @return The char is an alphanumeric character.
+ */
+[[nodiscard]] bool isAlphaNumeric(char c);
+
+/**
+ * If a string is entirely composed of alphanumeric characters (a-z, A-Z, 0-9).
+ * @param str The string.
+ * @return The string is entirely composed of alphanumeric characters.
+ */
+[[nodiscard]] bool isAlphaNumeric(std::string_view str);
+
[[nodiscard]] bool contains(std::string_view s, char c);
/// A very basic regex-like pattern checker for ASCII strings. Supports:
diff --git a/include/sourcepp/parser/Text.h b/include/sourcepp/parser/Text.h
index 1bb50a848..42162d543 100644
--- a/include/sourcepp/parser/Text.h
+++ b/include/sourcepp/parser/Text.h
@@ -100,48 +100,6 @@ class Tokenizer {
EscapeSequenceMap escapeSequences = getDefaultEscapeSequences();
};
-/**
- * If a char is a newline character.
- * @param c The char.
- * @return The char is a newline character.
- */
-[[nodiscard]] bool isNewLine(char c);
-
-/**
- * If a string is entirely composed of newline characters.
- * @param str The string.
- * @return The string is entirely composed of newline characters.
- */
-[[nodiscard]] bool isNewLine(std::string_view str);
-
-/**
- * If a char is a whitespace character.
- * @param c The char.
- * @return The char is a whitespace character.
- */
-[[nodiscard]] bool isWhitespace(char c);
-
-/**
- * If a string is entirely composed of whitespace characters.
- * @param str The string.
- * @return The string is entirely composed of whitespace characters.
- */
-[[nodiscard]] bool isWhitespace(std::string_view str);
-
-/**
- * If a char is a numerical character (0-9).
- * @param c The char.
- * @return The char is a numerical character.
- */
-[[nodiscard]] bool isNumber(char c);
-
-/**
- * If a string is entirely composed of numerical characters (0-9).
- * @param str The string.
- * @return The string is entirely composed of numerical characters.
- */
-[[nodiscard]] bool isNumber(std::string_view str);
-
/**
* Convert special characters like \\n to escaped special characters like \\\\n
* @param str The string to convert
diff --git a/include/vpkpp/format/PAK.h b/include/vpkpp/format/PAK.h
index cae1e41cd..5041a4841 100644
--- a/include/vpkpp/format/PAK.h
+++ b/include/vpkpp/format/PAK.h
@@ -26,6 +26,7 @@ class PAK : public PackFile {
public:
enum class Type {
PAK,
+ DAIKATANA,
SIN,
SIN_RELOADED,
HROT,
diff --git a/lang/c/include/vpkppc/format/PAK.h b/lang/c/include/vpkppc/format/PAK.h
index 02e52e370..e9ddfdf19 100644
--- a/lang/c/include/vpkppc/format/PAK.h
+++ b/lang/c/include/vpkppc/format/PAK.h
@@ -18,6 +18,7 @@ VPKPP_EXTERNVAR const char* VPKPP_SIN_EXTENSION;
VPKPP_EXTERN typedef enum {
VPKPP_PAK_TYPE_PAK,
+ VPKPP_PAK_TYPE_DAIKATANA,
VPKPP_PAK_TYPE_SIN,
VPKPP_PAK_TYPE_SIN_RELOADED,
VPKPP_PAK_TYPE_HROT,
@@ -39,6 +40,7 @@ namespace sourceppc::convert {
inline vpkpp::PAK::Type cast(vpkpp_pak_type_e value) {
switch (value) {
case VPKPP_PAK_TYPE_PAK: return vpkpp::PAK::Type::PAK;
+ case VPKPP_PAK_TYPE_DAIKATANA: return vpkpp::PAK::Type::DAIKATANA;
case VPKPP_PAK_TYPE_SIN: return vpkpp::PAK::Type::SIN;
case VPKPP_PAK_TYPE_SIN_RELOADED: return vpkpp::PAK::Type::SIN_RELOADED;
case VPKPP_PAK_TYPE_HROT: return vpkpp::PAK::Type::HROT;
@@ -49,6 +51,7 @@ inline vpkpp::PAK::Type cast(vpkpp_pak_type_e value) {
inline vpkpp_pak_type_e cast(vpkpp::PAK::Type value) {
switch (value) {
case vpkpp::PAK::Type::PAK: return VPKPP_PAK_TYPE_PAK;
+ case vpkpp::PAK::Type::DAIKATANA: return VPKPP_PAK_TYPE_DAIKATANA;
case vpkpp::PAK::Type::SIN: return VPKPP_PAK_TYPE_SIN;
case vpkpp::PAK::Type::SIN_RELOADED: return VPKPP_PAK_TYPE_SIN_RELOADED;
case vpkpp::PAK::Type::HROT: return VPKPP_PAK_TYPE_HROT;
diff --git a/lang/csharp/src/vpkpp/Format/PAK.cs b/lang/csharp/src/vpkpp/Format/PAK.cs
index d7e5a279f..a57a6cf3d 100644
--- a/lang/csharp/src/vpkpp/Format/PAK.cs
+++ b/lang/csharp/src/vpkpp/Format/PAK.cs
@@ -10,6 +10,7 @@ namespace sourcepp.vpkpp.Format;
public enum PAKType
{
PAK,
+ DAIKATANA,
SIN,
SIN_RELOADED,
HROT,
diff --git a/lang/python/src/vpkpp.h b/lang/python/src/vpkpp.h
index 814c9aa04..1152e56bd 100644
--- a/lang/python/src/vpkpp.h
+++ b/lang/python/src/vpkpp.h
@@ -243,6 +243,7 @@ inline void register_python(py::module_& m) {
py::enum_(cPAK, "Type")
.value("PAK", PAK::Type::PAK)
+ .value("DAIKATANA", PAK::Type::DAIKATANA)
.value("SIN", PAK::Type::SIN)
.value("SIN_RELOADED", PAK::Type::SIN_RELOADED)
.value("HROT", PAK::Type::HROT);
diff --git a/src/sourcepp/String.cpp b/src/sourcepp/String.cpp
index 57eafc875..c09e2ab11 100644
--- a/src/sourcepp/String.cpp
+++ b/src/sourcepp/String.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -19,6 +20,38 @@ std::mt19937& getRandomGenerator() {
using namespace sourcepp;
+bool string::isNewLine(char c) {
+ return c == '\r' || c == '\n';
+}
+
+bool string::isNewLine(std::string_view str) {
+ return std::ranges::all_of(str, [](char c) { return isNewLine(c); });
+}
+
+bool string::isWhitespace(char c) {
+ return std::isspace(c, std::locale::classic());
+}
+
+bool string::isWhitespace(std::string_view str) {
+ return std::ranges::all_of(str, [](char c) { return isWhitespace(c); });
+}
+
+bool string::isNumber(char c) {
+ return std::isdigit(c, std::locale::classic());
+}
+
+bool string::isNumber(std::string_view str) {
+ return std::ranges::all_of(str, [](char c) { return isNumber(c); });
+}
+
+bool string::isAlphaNumeric(char c) {
+ return std::isalnum(c, std::locale::classic());
+}
+
+bool string::isAlphaNumeric(std::string_view str) {
+ return std::ranges::all_of(str, [](char c) { return isAlphaNumeric(c); });
+}
+
bool string::contains(std::string_view s, char c) {
return std::ranges::find(s, c) != s.end();
}
diff --git a/src/sourcepp/parser/Text.cpp b/src/sourcepp/parser/Text.cpp
index 2e4be9cc9..e17179de2 100644
--- a/src/sourcepp/parser/Text.cpp
+++ b/src/sourcepp/parser/Text.cpp
@@ -1,7 +1,6 @@
#include
#include
-#include
#include
@@ -116,30 +115,6 @@ parser::text::Tokenizer::Result parser::text::Tokenizer::parse(std::string_view
return result;
}
-bool parser::text::isNewLine(char c) {
- return c == '\n' || c == '\r';
-}
-
-bool parser::text::isNewLine(std::string_view str) {
- return std::ranges::all_of(str, [](char c) { return isNewLine(c); });
-}
-
-bool parser::text::isWhitespace(char c) {
- return c == ' ' || c == '\a' || c == '\f' || c == '\t' || c == '\v' || isNewLine(c);
-}
-
-bool parser::text::isWhitespace(std::string_view str) {
- return std::ranges::all_of(str, [](char c) { return isWhitespace(c); });
-}
-
-bool parser::text::isNumber(char c) {
- return std::isdigit(c);
-}
-
-bool parser::text::isNumber(std::string_view str) {
- return std::ranges::all_of(str, [](char c) { return isNumber(c); });
-}
-
std::string parser::text::convertSpecialCharsToEscapes(std::string_view str, const EscapeSequenceMap& escapeSequences) {
// Reverse escape sequences map (assume that it's bidirectional)
EscapeSequenceMap specialSequences;
@@ -177,13 +152,13 @@ std::string parser::text::convertEscapesToSpecialChars(std::string_view str, con
}
void parser::text::eatWhitespace(BufferStream& stream) {
- while (stream.tell() < stream.size() && isWhitespace(stream.read())) {}
- if (stream.tell() == stream.size() && isWhitespace(stream.seek(-1, std::ios::cur).read())) return;
+ while (stream.tell() < stream.size() && string::isWhitespace(stream.read())) {}
+ if (stream.tell() == stream.size() && string::isWhitespace(stream.seek(-1, std::ios::cur).read())) return;
stream.seek(-1, std::ios::cur);
}
void parser::text::eatSingleLineComment(BufferStream& stream) {
- while (stream.tell() < stream.size() && !isNewLine(stream.read())) {}
+ while (stream.tell() < stream.size() && !string::isNewLine(stream.read())) {}
}
void parser::text::eatMultiLineComment(BufferStream& stream, std::string_view multiLineCommentEnd) {
@@ -248,10 +223,10 @@ std::string parser::text::readString(BufferStream& stream, std::initializer_list
out += c;
}
- for (c = stream.read(); (stopAtWhitespace && !isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
+ for (c = stream.read(); (stopAtWhitespace && !string::isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
if (!escapeSequences.empty() && c == '\\') {
auto n = stream.read();
- if (stopAtWhitespace && isWhitespace(n)) {
+ if (stopAtWhitespace && string::isWhitespace(n)) {
break;
}
if (escapeSequences.contains(n)) {
@@ -287,10 +262,10 @@ std::string_view parser::text::readStringToBuffer(BufferStream& stream, BufferSt
backing << c;
}
- for (c = stream.read(); (stopAtWhitespace && !isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
+ for (c = stream.read(); (stopAtWhitespace && !string::isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
if (!escapeSequences.empty() && c == '\\') {
auto n = stream.read();
- if (stopAtWhitespace && isWhitespace(n)) {
+ if (stopAtWhitespace && string::isWhitespace(n)) {
break;
}
if (escapeSequences.contains(n)) {
@@ -306,6 +281,7 @@ std::string_view parser::text::readStringToBuffer(BufferStream& stream, BufferSt
}
backing << '\0';
+ // ReSharper disable once CppRedundantCastExpression
return {reinterpret_cast(backing.data()) + startSpan, static_cast(backing.tell() - 1 - startSpan)};
}
@@ -320,11 +296,11 @@ std::string_view parser::text::readUnquotedStringToBuffer(BufferStream& stream,
std::string parser::text::readUnquotedString(BufferStream& stream, std::string_view end, const EscapeSequenceMap& escapeSequences) {
std::string out;
- for (char c = stream.read(); !isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read()) {
+ for (char c = stream.read(); !string::isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read()) {
if (!escapeSequences.empty() && c == '\\') {
if (auto n = stream.read(); escapeSequences.contains(n)) {
out += escapeSequences.at(n);
- } else if (isWhitespace(n) || end.find(n) != std::string_view::npos) {
+ } else if (string::isWhitespace(n) || end.find(n) != std::string_view::npos) {
break;
} else {
out += c;
@@ -341,11 +317,11 @@ std::string parser::text::readUnquotedString(BufferStream& stream, std::string_v
std::string_view parser::text::readUnquotedStringToBuffer(BufferStream& stream, BufferStream& backing, std::string_view end, const EscapeSequenceMap& escapeSequences) {
const auto startSpan = backing.tell();
- for (char c = stream.read(); !isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read()) {
+ for (char c = stream.read(); !string::isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read()) {
if (!escapeSequences.empty() && c == '\\') {
if (auto n = stream.read(); escapeSequences.contains(n)) {
backing << escapeSequences.at(n);
- } else if (isWhitespace(n) || end.find(n) != std::string_view::npos) {
+ } else if (string::isWhitespace(n) || end.find(n) != std::string_view::npos) {
break;
} else {
backing << c << n;
@@ -356,5 +332,6 @@ std::string_view parser::text::readUnquotedStringToBuffer(BufferStream& stream,
}
backing << '\0';
+ // ReSharper disable once CppRedundantCastExpression
return {reinterpret_cast(backing.data()) + startSpan, static_cast(backing.tell() - 1 - startSpan)};
}
diff --git a/src/toolpp/CmdSeq.cpp b/src/toolpp/CmdSeq.cpp
index ebf834492..565c2ea03 100644
--- a/src/toolpp/CmdSeq.cpp
+++ b/src/toolpp/CmdSeq.cpp
@@ -171,9 +171,10 @@ void CmdSeq::parseKeyValuesHPP(const std::filesystem::path& path) {
for (const auto& kvCommand : kvSequence.getChildren()) {
auto& [enabled, special, executable, arguments, ensureFileExists, pathToTheoreticallyExistingFile, useProcessWindow, waitForKeypress] = seqCommands.emplace_back();
string::toBool(kvCommand["enable"].getValue(), enabled);
- const auto specialCmd = kvCommand["specialcmd"].getValue();
- if (parser::text::isNumber(specialCmd)) {
- string::toInt(specialCmd, reinterpret_cast&>(special));
+ if (
+ const auto specialCmd = kvCommand["specialcmd"].getValue();
+ string::toInt(specialCmd, reinterpret_cast&>(special))
+ ) {
if (special == Command::SPECIAL_COPY_FILE_IF_EXISTS_ALIAS) {
special = Command::Special::COPY_FILE_IF_EXISTS;
}
@@ -197,9 +198,10 @@ void CmdSeq::parseKeyValuesStrata(const std::filesystem::path& path) {
for (const auto& kvCommand : kvSequence.getChildren()) {
auto& [enabled, special, executable, arguments, ensureFileExists, pathToTheoreticallyExistingFile, useProcessWindow, waitForKeypress] = seqCommands.emplace_back();
string::toBool(kvCommand["enabled"].getValue(), enabled);
- const auto specialCmd = kvCommand["special_cmd"].getValue();
- if (parser::text::isNumber(specialCmd)) {
- string::toInt(specialCmd, reinterpret_cast&>(special));
+ if (
+ const auto specialCmd = kvCommand["special_cmd"].getValue();
+ string::toInt(specialCmd, reinterpret_cast&>(special))
+ ) {
if (special == Command::SPECIAL_COPY_FILE_IF_EXISTS_ALIAS) {
special = Command::Special::COPY_FILE_IF_EXISTS;
}
diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp
index f709f78fa..aed302576 100644
--- a/src/vpkpp/format/PAK.cpp
+++ b/src/vpkpp/format/PAK.cpp
@@ -5,15 +5,57 @@
#include
#include
+#include
using namespace sourcepp;
using namespace vpkpp;
+namespace {
+
+std::vector decompressDaikatanaEntryData(std::span inputData_) {
+ BufferStreamReadOnly inStream{inputData_};
+
+ std::vector out;
+ BufferStream outStream{out};
+
+ while (inStream.tell() < inStream.size()) {
+ if (
+ const auto code = inStream.read();
+ code < 64
+ ) {
+ // Uncompressed block
+ outStream << inStream.read_span(code + 1);
+ } else if (code < 128) {
+ // Run length zero block
+ outStream.pad(code - 62);
+ } else if (code < 192) {
+ // Run length encoded block
+ const auto value = inStream.read();
+ outStream.pad(code - 126, value);
+ } else if (code < 254) {
+ // Reference previous data
+ const auto offset = inStream.read();
+ outStream << outStream.at_bytes_u(code - 190, outStream.size() - offset - 2);
+ } else if (code == 254) {
+ // ReSharper disable once CppRedundantControlFlowJump
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ out.resize(outStream.size());
+ return out;
+}
+
+} // namespace
+
std::unique_ptr PAK::create(const std::string& path, Type type) {
{
FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
switch (type) {
case Type::PAK:
+ case Type::DAIKATANA:
stream << PAK_SIGNATURE;
break;
case Type::SIN:
@@ -45,6 +87,9 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
FileStream reader{pak->fullFilePath};
reader.seek_in(0);
+ // Get total file size
+ const auto pakFileLength = std::filesystem::file_size(path);
+
if (const auto signature = reader.read(); signature == PAK_SIGNATURE) {
pak->type = Type::PAK;
} else if (signature == PAK_SIN_SIGNATURE) {
@@ -91,9 +136,63 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
}
}
} else {
+ // Directory offset and size
const auto directoryOffset = reader.read();
- // Directory size / file entry size
- const auto fileCount = reader.read() / (sizeof(uint32_t) * 2 + pak->getFilenameLength());
+ const auto directorySize = reader.read();
+
+ // Check if it's a Daikatana PAK
+ bool isDaikatana = directorySize % 64 != 0;
+ const auto entrySize = sizeof(uint32_t) * 2 + pak->getFilenameLength();
+ const auto dkEntrySize = sizeof(uint32_t) * 4 + pak->getFilenameLength();
+
+ // Check again if it's a Daikatana PAK for this edge case
+ if (directorySize % 576 == 0) {
+ const auto numEntries = directorySize / dkEntrySize;
+
+ for (size_t i = 0; i < numEntries; i++) {
+ // Check filename
+ auto entryPath = pak->cleanEntryPath(reader.read_string(pak->getFilenameLength()));
+
+ size_t j;
+ for (j = 0; j < pak->getFilenameLength(); j++) {
+ if (entryPath[j] == '\0' || !string::isAlphaNumeric(entryPath[j])) {
+ break;
+ }
+ }
+ if (j == 0 || !string::isAlphaNumeric(entryPath)) {
+ isDaikatana = false;
+ break;
+ }
+
+ const auto offset = reader.read();
+ auto length = reader.read();
+
+ // check compression
+ const auto compressedLength = reader.read();
+ const auto compressedType = reader.read();
+ if (compressedType != 0 && compressedType != 1) {
+ isDaikatana = false;
+ break;
+ }
+
+ // check offset
+ if (compressedType == 1) {
+ length = compressedLength;
+ }
+ if (offset + length > pakFileLength) {
+ isDaikatana = false;
+ break;
+ }
+ }
+ }
+
+ // Set the type if we truly think it's a Daikatana PAK
+ if (isDaikatana) {
+ pak->type = Type::DAIKATANA;
+ }
+
+ // File count
+ const auto fileCount = directorySize / (isDaikatana ? dkEntrySize : entrySize);
reader.seek_in(directoryOffset);
for (uint32_t i = 0; i < fileCount; i++) {
@@ -104,6 +203,17 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
entry.offset = reader.read();
entry.length = reader.read();
+ // Read extra data
+ if (isDaikatana) {
+ const auto compressedLength = reader.read();
+ if (
+ const auto compressedType = reader.read();
+ compressedType == 1
+ ) {
+ entry.compressedLength = compressedLength;
+ }
+ }
+
pak->entries.emplace(entryPath, entry);
if (callback) {
@@ -131,6 +241,12 @@ std::optional> PAK::readEntry(const std::string& path_) c
return std::nullopt;
}
stream.seek_in_u(entry->offset);
+
+ // Check if we need to decompress the Daikatana PAK file
+ if (this->type == Type::DAIKATANA && entry->compressedLength > 0) {
+ return ::decompressDaikatanaEntryData(stream.read_bytes(entry->compressedLength));
+ }
+
return stream.read_bytes(entry->length);
}
@@ -142,6 +258,11 @@ void PAK::addEntryInternal(Entry& entry, const std::string&, std::vectortype == Type::DAIKATANA) {
+ return false;
+ }
+
// Get the proper file output folder
const std::string outputDir = this->getBakeOutputDir(outputDir_);
const std::string outputPath = outputDir + '/' + this->getFilename();
@@ -165,7 +286,7 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions, const EntryCallback&
if (this->type == Type::SIN_RELOADED) {
const auto oldSize = nameData.size();
nameData.resize(oldSize + path.size() + 1);
- memcpy(&nameData[oldSize], path.data(), path.size());
+ std::memcpy(&nameData[oldSize], path.data(), path.size());
nameOffsets.push_back(oldSize);
}
} else {
@@ -181,9 +302,7 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions, const EntryCallback&
// Signature
stream.write(this->getSignature());
- // Header
if (this->type == Type::SIN_RELOADED) {
- // Reserved value
// Basically a version identifier, but not used...?
stream.write(0);
@@ -266,6 +385,7 @@ void PAK::setType(Type type_) {
uint32_t PAK::getSignature() const {
switch (this->type) {
case Type::PAK:
+ case Type::DAIKATANA:
return PAK_SIGNATURE;
case Type::SIN:
return PAK_SIN_SIGNATURE;
@@ -280,6 +400,7 @@ uint32_t PAK::getSignature() const {
uint8_t PAK::getFilenameLength() const {
switch (this->type) {
case Type::PAK:
+ case Type::DAIKATANA:
return PAK_FILENAME_MAX_SIZE;
case Type::SIN:
return PAK_SIN_FILENAME_MAX_SIZE;
diff --git a/src/vpkpp/format/VPK_VTMB.cpp b/src/vpkpp/format/VPK_VTMB.cpp
index a40e5c97a..3b2b1a70d 100644
--- a/src/vpkpp/format/VPK_VTMB.cpp
+++ b/src/vpkpp/format/VPK_VTMB.cpp
@@ -30,7 +30,7 @@ std::unique_ptr VPK_VTMB::open(const std::string& path, const EntryCal
// Extra check to make sure this is a VTMB VPK path
const auto stem = std::filesystem::path{path}.stem().string();
- if (stem.length() != 7 || !stem.starts_with("pack") || !parser::text::isNumber(stem.substr(4))) {
+ if (stem.length() != 7 || !stem.starts_with("pack") || !string::isNumber(stem.substr(4))) {
return nullptr;
}
diff --git a/test/vpkpp.cpp b/test/vpkpp.cpp
index f7a2e0935..c6a0dcef1 100644
--- a/test/vpkpp.cpp
+++ b/test/vpkpp.cpp
@@ -27,6 +27,20 @@ TEST(vpkpp, ore_read) {
EXPECT_TRUE(ore->hasEntry("startup.cfg"));
}
+TEST(vpkpp, pak_daikatana_read) {
+ const auto pak = PackFile::open(ASSET_ROOT "vpkpp/pak/daikatana.pak");
+ ASSERT_TRUE(pak);
+ VPKPP_PRINT_ALL_PATHS(pak);
+ EXPECT_EQ(pak->getEntryCount(), 1);
+ EXPECT_TRUE(pak->hasEntry("palette.bmp"));
+
+ const auto entry = pak->readEntry("palette.bmp");
+ ASSERT_TRUE(entry);
+ ASSERT_EQ(entry->size(), 65078);
+ EXPECT_EQ((*entry)[0], static_cast('B'));
+ EXPECT_EQ((*entry)[1], static_cast('M'));
+}
+
TEST(vpkpp, pak_sin_read) {
const auto sin = PackFile::open(ASSET_ROOT "vpkpp/sin/test.sin");
ASSERT_TRUE(sin);