diff --git a/include/dmxpp/dmxpp.h b/include/dmxpp/dmxpp.h index 324cd0008..e848297f8 100644 --- a/include/dmxpp/dmxpp.h +++ b/include/dmxpp/dmxpp.h @@ -6,10 +6,15 @@ class BufferStream; namespace dmxpp { +constexpr int MAX_FORMAT = 64; +constexpr int MAX_HEADER = 40 + 2 * MAX_FORMAT; // DMX_MAX_HEADER_LENGTH in SDK + namespace Format { constexpr std::string_view TEXT = "keyvalues2"; constexpr std::string_view BINARY = "binary"; +constexpr std::string_view SRCTOOLS_UTF8_TEXT = "unicode_keyvalues2"; +constexpr std::string_view SRCTOOLS_UTF8_BINARY = "unicode_binary"; } // namespace Format diff --git a/src/dmxpp/dmxpp.cpp b/src/dmxpp/dmxpp.cpp index 567f030c0..87187ede6 100644 --- a/src/dmxpp/dmxpp.cpp +++ b/src/dmxpp/dmxpp.cpp @@ -14,17 +14,26 @@ DMX::DMX(const std::byte* dmxData, std::size_t dmxSize) { BufferStreamReadOnly stream{dmxData, dmxSize}; - auto header = stream.read_string(); + // The header is terminated by a newline, but can be at most MAX_HEADER long. + // For binary formats that's then followed by a null terminator. + std::string header; + while (header.length() < MAX_HEADER) { + char temp = stream.read(); + if (temp == '\n') { + break; + } + header += temp; + } if (header.length() < 37) { // Minimum possible header length - early "binary_v2" version, "keyvalues2_v1" unsupported return; } - char encodingTypeData[64]; + char encodingTypeData[MAX_FORMAT]; int32_t encodingVersionData; - char formatTypeData[64]; + char formatTypeData[MAX_FORMAT]; int32_t formatVersionData; #ifdef _WIN32 - sscanf_s(header.c_str(), "\n", encodingTypeData, 64, &encodingVersionData, formatTypeData, 64, &formatVersionData); + sscanf_s(header.c_str(), "\n", encodingTypeData, MAX_FORMAT, &encodingVersionData, formatTypeData, MAX_FORMAT, &formatVersionData); #else std::sscanf(header.c_str(), "\n", encodingTypeData, &encodingVersionData, formatTypeData, &formatVersionData); #endif @@ -41,9 +50,10 @@ DMX::DMX(const std::byte* dmxData, std::size_t dmxSize) { return; } - if (this->encodingType == Format::BINARY) { + // Srctools formats indicate that all strings are specifically UTF-8. + if (this->encodingType == Format::BINARY || this->encodingType == Format::SRCTOOLS_UTF8_BINARY) { this->opened = this->openBinary(stream); - } else if (this->encodingType == Format::TEXT) { + } else if (this->encodingType == Format::TEXT || this->encodingType == Format::SRCTOOLS_UTF8_TEXT) { this->opened = this->openText(dmxData, dmxSize); } } @@ -92,6 +102,11 @@ bool DMX::openBinary(BufferStream& stream) { const bool elementNamesAreStoredInStringList = this->encodingVersion >= 4; const bool stringValuesAreStoredInStringList = this->encodingVersion >= 4; + // Eat the null terminator for the header. + if (stream.read() != 0) { + return false; + } + // String list std::vector stringList; uint32_t stringCount; @@ -140,7 +155,8 @@ bool DMX::openBinary(BufferStream& stream) { auto size = reader.read(); out.reserve(size); for (int i = 0; i < size; i++) { - out.push_back(std::get(readValue(reader, Value::arrayIDToInnerID(type_), true))); + // String arrays are always inline. + out.push_back(std::get(readValue(reader, Value::arrayIDToInnerID(type_), false))); } return out; };