Refuse to save if game state can't be represented as valid WML (#2375)

Such a save file can't be loaded anyway.
This way, at least the player can't overwrite an existing, working save
with one that cannot be loaded.
This commit is contained in:
Jyrki Vesterinen 2018-01-17 21:05:44 +02:00
parent 5df406869a
commit 3bc36efa58
3 changed files with 44 additions and 0 deletions

View File

@ -25,10 +25,12 @@
#include "utils/const_clone.hpp"
#include "utils/functional.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <istream>
#include <locale>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/get.hpp>
@ -1303,6 +1305,33 @@ void swap(config& lhs, config& rhs)
lhs.swap(rhs);
}
bool config::is_valid_wml_tag_name(config_key_type name)
{
if(name == "") {
// Empty strings not allowed
return false;
} else if(name[0] == '_') {
// Underscore can't be the first character
return false;
} else {
return std::all_of(name.begin(), name.end(), [](const char& c)
{
// Only alphanumeric ASCII characters and underscores are allowed
return std::isalnum(static_cast<unsigned char>(c), std::locale::classic()) || c == '_';
});
}
}
bool config::validate_wml() const
{
return std::all_of(children_.begin(), children_.end(), [](const child_map::value_type& pair)
{
return is_valid_wml_tag_name(pair.first) &&
std::all_of(pair.second.begin(), pair.second.end(),
[](const std::unique_ptr<config>& c) { return c->validate_wml(); });
});
}
bool operator==(const config& a, const config& b)
{
a.check_valid(b);

View File

@ -761,6 +761,17 @@ public:
//this is a cheap O(1) operation
void swap(config& cfg);
/**
* Returns true for valid WML tag names, false for all other strings.
*/
static bool is_valid_wml_tag_name(config_key_type name);
/**
* Returns true if this object represents valid WML,
* i.e. can be saved to disk and again loaded by the WML parser.
*/
bool validate_wml() const;
private:
/**
* Removes the child at position @a pos of @a l.

View File

@ -637,6 +637,10 @@ void ingame_savegame::create_filename()
void ingame_savegame::write_game(config_writer &out) {
log_scope("write_game");
if(!gamestate().get_starting_pos().validate_wml()) {
throw game::save_game_failed(_("Game state is corrupted"));
}
savegame::write_game(out);
gamestate().write_carryover(out);