Files
DarkflameServer/dDatabase/GameDatabase/ITables/ICharXml.h
Aaron Kimbrell 341ce89a6a feat: refuse stale character saves with a save generation
Every character gets a save generation in charxml. A world bumps it when it loads the character for
play, and every save from that world only goes through while the stored generation is still the
one it loaded or last saved (and moves it on). Dashboard edits, restores and maintenance writes bump
it too. A world that lost the character to another world (a disconnect noticed late, a zone
transfer, an instance migration) or to a dashboard edit can no longer overwrite the newer data: the
save is refused, logged and audited as stale_save_refused, the world stops saving that character
and a player still connected to it is disconnected with the save failure reason so they reload.

Fixes #639

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:51 -05:00

49 lines
2.2 KiB
C++

#ifndef __ICHARXML__H__
#define __ICHARXML__H__
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
class ICharXml {
public:
// Get the character xml for the given character id.
virtual std::string GetCharacterXml(const LWOOBJID charId) = 0;
// Overwrite the character xml for the given character id (dashboard edits, restores, maintenance). Bumps the save
// generation, so a world still holding the older version can't save over this one.
virtual void UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) = 0;
/**
* Stale save guard. Every write to a character's xml bumps its save generation: a world loading the character to
* play it, a world saving it and the dashboard writing it. A world keeps the generation it loaded (or last saved)
* and only saves over that one, so a world that lost the character to another world or to the dashboard can't
* overwrite the newer data.
*/
struct CharacterXml {
std::string xml;
uint64_t generation{};
};
// Take over the character for playing: bump its save generation and return the xml with the new generation.
// nullopt when the character has no xml.
virtual std::optional<CharacterXml> ClaimCharacterXml(const LWOOBJID charId) = 0;
// Save if the stored generation is still `generation`: the xml is written, the generation becomes generation + 1
// and true is returned. Otherwise nothing is written and false is returned (someone newer saved it).
virtual bool SaveCharacterXml(const LWOOBJID charId, const std::string_view lxfml, const uint64_t generation) = 0;
// The stored save generation (0 when the character has no xml)
virtual uint64_t GetCharacterSaveGeneration(const LWOOBJID charId) = 0;
// Insert the character xml for the given character id.
virtual void InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) = 0;
// Get paginated list of characters with optional search/filtering for DataTables
// Returns a JSON-formatted string with the character data and metadata
virtual std::string GetCharactersTable(uint32_t start, uint32_t length, const std::string_view search = "", uint32_t orderColumn = 0, bool orderAsc = true) = 0;
};
#endif //!__ICHARXML__H__