You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

118 lines
4.8 KiB

#include "file.hpp"
#include <fmt/format.h>
#include "engine/assets.hpp"
#include "utils/language.h"
namespace devilution {
tl::expected<DataFile, DataFile::Error> 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<char[]> 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<void, DataFile::Error> DataFile::parseHeader(ColumnDefinition *begin, ColumnDefinition *end, tl::function_ref<tl::expected<uint8_t, ColumnDefinition::Error>(std::string_view)> mapper)
{
std::bitset<std::numeric_limits<uint8_t>::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