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) ✅ @@ -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);