diff options
-rw-r--r-- | src/BlockTypePalette.cpp | 175 | ||||
-rw-r--r-- | tests/BlockTypeRegistry/BlockTypePaletteTest.cpp | 4 |
2 files changed, 132 insertions, 47 deletions
diff --git a/src/BlockTypePalette.cpp b/src/BlockTypePalette.cpp index b8f962f5d..b452023c4 100644 --- a/src/BlockTypePalette.cpp +++ b/src/BlockTypePalette.cpp @@ -7,6 +7,30 @@ +/** Returns the index into aString >= aStartIdx at which the next separator occurs. +Separator is one of \t, \n or \r. +Returns AString::npos if no such separator. */ +static size_t findNextSeparator(const AString & aString, size_t aStartIdx = 0) +{ + for (size_t i = aStartIdx, len = aString.length(); i < len; ++i) + { + switch (aString[i]) + { + case '\t': + case '\n': + case '\r': + { + return i; + } + } + } + return AString::npos; +} + + + + + BlockTypePalette::BlockTypePalette(): mMaxIndex(0) { @@ -196,89 +220,150 @@ void BlockTypePalette::loadFromJsonString(const AString & aJsonPalette) void BlockTypePalette::loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade) { - auto lines = StringSplitAndTrim(aTsvPalette, "\n"); + static const AString hdrTsvRegular = "BlockTypePalette"; + static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette"; + + // Check the file signature: + auto idx = findNextSeparator(aTsvPalette); + if ((idx == AString::npos) || (aTsvPalette[idx] == '\t')) + { + throw LoadFailedException("Invalid signature"); + } + auto signature = aTsvPalette.substr(0, idx); + bool isUpgrade = (signature == hdrTsvUpgrade); + if (!isUpgrade && (signature != hdrTsvRegular)) + { + throw LoadFailedException("Unknown signature"); + } + if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF: + { + idx += 1; + } // Parse the header: - int fileVersion = 0; + bool hasHadVersion = false; AString commonPrefix; - auto numLines = lines.size(); - for (size_t idx = 1; idx < numLines; ++idx) + int line = 2; + auto len = aTsvPalette.length(); + while (true) { - const auto & line = lines[idx]; - if (line.empty()) + auto keyStart = idx + 1; + auto keyEnd = findNextSeparator(aTsvPalette, idx + 1); + if (keyEnd == AString::npos) + { + throw LoadFailedException(Printf("Invalid header key format on line %u", line)); + } + if (keyEnd == idx + 1) // Empty line, end of headers { - // End of headers, erase them from lines[] and go parse the data - lines.erase(lines.begin(), lines.begin() + static_cast<AStringVector::difference_type>(idx) + 1); + if (aTsvPalette[keyEnd] == '\r') // CR of the CRLF pair, skip the LF: + { + ++keyEnd; + } + idx = keyEnd; + ++line; break; } - auto s = StringSplit(line, "\t"); - if (s.size() != 2) + auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1); + if ((valueEnd == AString::npos) || (aTsvPalette[valueEnd] == '\t')) { - throw LoadFailedException(Printf("Invalid header format on line %u", idx + 1)); + throw LoadFailedException(Printf("Invalid header value format on line %u", line)); } - if (s[0] == "FileVersion") + auto key = aTsvPalette.substr(keyStart, keyEnd - keyStart); + if (key == "FileVersion") { - try + unsigned version = 0; + auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); + if (!StringToInteger(value, version)) { - fileVersion = std::stoi(s[1]); + throw LoadFailedException("Invalid FileVersion value"); } - catch (const std::exception & exc) + else if (version != 1) { - throw LoadFailedException(Printf("Invalid file version: \"%d\" (%s)", s[1], exc.what())); + throw LoadFailedException(Printf("Unknown FileVersion: %u. Only version 1 is supported.", version)); } + hasHadVersion = true; + } + else if (key == "CommonPrefix") + { + commonPrefix = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); } - else if (s[0] == "CommonPrefix") + idx = valueEnd; + if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF: { - commonPrefix = s[1]; + ++idx; } + ++line; } - if (fileVersion != 1) + if (!hasHadVersion) { - throw LoadFailedException(Printf("Unknown file version (%d), only version 1 is supported", fileVersion)); + throw LoadFailedException("No FileVersion value"); } // Parse the data: - size_t minSplit = aIsUpgrade ? 3 : 2; - for (const auto & line: lines) + while (idx + 1 < len) { - auto s = StringSplit(line, "\t"); - auto numSplit = s.size(); - if (numSplit < minSplit) + auto lineStart = idx + 1; + auto idEnd = findNextSeparator(aTsvPalette, lineStart); + if ((idEnd == AString::npos) || (aTsvPalette[idEnd] != '\t')) { - throw LoadFailedException(Printf("Not enough values on data line: \"%s\"", line)); + throw LoadFailedException(Printf("Incomplete data on line %u (id)", line)); } UInt32 id; - try + if (!StringToInteger(aTsvPalette.substr(lineStart, idEnd - lineStart), id)) { - id = static_cast<UInt32>(std::stoi(s[0])); + throw LoadFailedException(Printf("Failed to parse id on line %u", line)); } - catch (const std::exception & exc) + size_t metaEnd = idEnd; + if (isUpgrade) { - throw LoadFailedException(Printf("Invalid block ID: \"%s\" (%s)", s[0], exc.what())); + metaEnd = findNextSeparator(aTsvPalette, idEnd + 1); + if ((metaEnd == AString::npos) || (aTsvPalette[metaEnd] != '\t')) + { + throw LoadFailedException(Printf("Incomplete data on line %u (meta)", line)); + } + UInt32 meta = 0; + if (!StringToInteger(aTsvPalette.substr(idEnd + 1, metaEnd - idEnd - 1), meta)) + { + throw LoadFailedException(Printf("Failed to parse meta on line %u", line)); + } + if (meta > 15) + { + throw LoadFailedException(Printf("Invalid meta value on line %u: %u", line, meta)); + } + id = (id * 16) | meta; + } + auto blockTypeEnd = findNextSeparator(aTsvPalette, metaEnd + 1); + if (blockTypeEnd == AString::npos) + { + throw LoadFailedException(Printf("Incomplete data on line %u (blockTypeName)", line)); } - size_t idx = 1; - if (aIsUpgrade) + auto blockTypeName = aTsvPalette.substr(metaEnd + 1, blockTypeEnd - metaEnd - 1); + auto blockStateEnd = blockTypeEnd; + AStringMap blockState; + while (aTsvPalette[blockStateEnd] == '\t') { - id = id * 16; - try + auto keyEnd = findNextSeparator(aTsvPalette, blockStateEnd + 1); + if ((keyEnd == AString::npos) || (aTsvPalette[keyEnd] != '\t')) { - id = id + static_cast<UInt32>(Clamp(std::stoi(s[1]), 0, 15)); + throw LoadFailedException(Printf("Incomplete data on line %u (blockState key)", line)); } - catch (const std::exception & exc) + auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1); + if (valueEnd == AString::npos) { - throw LoadFailedException(Printf("Invalid block meta: \"%s\" (%s)", s[1], exc.what())); + throw LoadFailedException(Printf("Incomplete data on line %u (blockState value)", line)); } - idx = 2; + auto key = aTsvPalette.substr(blockStateEnd + 1, keyEnd - blockStateEnd - 1); + auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); + blockState[key] = value; + blockStateEnd = valueEnd; } - const auto & blockTypeName = s[idx]; - idx += 1; - std::map<AString, AString> state; - while (idx + 1 < numSplit) + addMapping(id, commonPrefix + blockTypeName, std::move(blockState)); + ++line; + if (aTsvPalette[blockStateEnd] == '\r') // CR of the CRLF pair, skip the LF: { - state[s[idx]] = s[idx + 1]; - idx += 2; + ++blockStateEnd; } - addMapping(id, commonPrefix + blockTypeName, state); + idx = blockStateEnd; } } diff --git a/tests/BlockTypeRegistry/BlockTypePaletteTest.cpp b/tests/BlockTypeRegistry/BlockTypePaletteTest.cpp index 995552085..1a2341bd9 100644 --- a/tests/BlockTypeRegistry/BlockTypePaletteTest.cpp +++ b/tests/BlockTypeRegistry/BlockTypePaletteTest.cpp @@ -229,8 +229,8 @@ static void testLoadTsvRegular(void) auto str = "\ BlockTypePalette\r\n\ FileVersion\t1\n\ -CommonPrefix\tminecraft:\r\n\ -\n\ +CommonPrefix\tminecraft:\n\ +\r\n\ 0\tair\r\n\ 1\tstone\n\ 2\tgrass\tsnow_covered\t0\n\ |