#include "file.hpp" #include #include "engine/assets.hpp" #include "utils/language.h" namespace devilution { tl::expected DataFile::load(std::string_view path) { AssetRef ref = FindAsset(path); if (!ref.ok()) return tl::unexpected { Error::NotFound }; const size_t size = ref.size(); // TODO: It should be possible to stream the data file contents instead of copying the whole thing into memory std::unique_ptr data { new char[size] }; { AssetHandle handle = OpenAsset(std::move(ref)); if (!handle.ok()) return tl::unexpected { Error::OpenFailed }; if (size > 0 && !handle.read(data.get(), size)) return tl::unexpected { Error::BadRead }; } return DataFile { std::move(data), size }; } void DataFile::reportFatalError(Error code, std::string_view fileName) { switch (code) { case Error::NotFound: case Error::OpenFailed: case Error::BadRead: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} */ "Unable to load data from file {0}")), fileName)); case Error::NoContent: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} */ "{0} is incomplete, please check the file contents.")), fileName)); case Error::NotEnoughColumns: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} */ "Your {0} file doesn't have the expected columns, please make sure it matches the documented format.")), fileName)); } } void DataFile::reportFatalFieldError(std::errc code, std::string_view fileName, std::string_view fieldName, const DataFileField &field) { switch (code) { case std::errc::invalid_argument: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ "Non-numeric value {0} for {1} in {2} at row {3} and column {4}")), field.currentValue(), fieldName, fileName, field.row(), field.column())); case std::errc::result_out_of_range: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ "Out of range value {0} for {1} in {2} at row {3} and column {4}")), field.currentValue(), fieldName, fileName, field.row(), field.column())); default: app_fatal(fmt::format(fmt::runtime(_( /* TRANSLATORS: Fallback error message when an error occurs while parsing a data file and we can't determine the cause. Arguments are {file name}, {row/record number}, {column/field number} */ "Unexpected error while reading {0} at row {1} and column {2}")), fileName, field.row(), field.column())); } } tl::expected DataFile::parseHeader(ColumnDefinition *begin, ColumnDefinition *end, tl::function_ref(std::string_view)> mapper) { std::bitset::max()> seenColumns; unsigned lastColumn = 0; RecordIterator firstRecord { data(), data() + size(), false }; for (DataFileField field : *firstRecord) { if (begin == end) { // All key columns have been identified break; } auto mapResult = mapper(*field); if (!mapResult.has_value()) { // not a key column continue; } uint8_t columnType = mapResult.value(); if (seenColumns.test(columnType)) { // Repeated column? unusual, maybe this should be an error continue; } seenColumns.set(columnType); unsigned skipColumns = 0; if (field.column() > lastColumn) skipColumns = field.column() - lastColumn - 1; lastColumn = field.column(); *begin = { columnType, skipColumns }; ++begin; } // Incrementing the iterator causes it to read to the end of the record in case we broke early (maybe there were extra columns) ++firstRecord; if (firstRecord == this->end()) { return tl::unexpected { Error::NoContent }; } body_ = firstRecord.data(); if (begin != end) { return tl::unexpected { Error::NotEnoughColumns }; } return {}; } } // namespace devilution