Files
sacred_k/src/mods/charfile.h
T
khallaandClaude Opus 5.5 feebe72ab7 Characters: reset a difficulty
"Zuruecksetzen" in the load window lists the selected character's worlds
and asks for confirmation. The world goes (a copy in characters\deleted),
with an autosave of it, and the shared hero's story completion of that
difficulty is cleared. The dialog is one generic list (title + entries).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 22:04:41 +02:00

104 lines
4.2 KiB
C++

#pragma once
// Character files (docs/characters.md): one file per character with its shared
// hero and one world per difficulty, plus reading the game's save files.
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace mods::characters {
using Bytes = std::vector<uint8_t>;
// Bronze, Silber, Gold, Platin, Niob (g_gameLevel 0..4).
constexpr int kDifficultyCount = 5;
// The campaigns (g_campaign 0x182cdc8): Ancaria 0, Underworld 1.
constexpr int kCampaignAncaria = 0;
constexpr int kCampaignUnderworld = 1;
constexpr int kCampaignCount = 2;
// What the game's save files (GAME.PAK "AMS", hero files .pax "AMH") say about
// their hero: header 0x100 bytes, then a table of 64 chunks {type, offset,
// size}; chunk 0xc3 is the hero header.
struct SaveInfo {
std::wstring name; // hero header +8, UTF-16
uint32_t heroClass = 0; // hero header +4: 1 Seraphim .. 9 Daemonin
uint32_t level = 0; // hero header +0
// Story completions per difficulty (hero header +0xed.., cStatsManager
// +0x5704; the hero file's are the hero's, loadHero sets them).
std::array<uint8_t, kDifficultyCount> storyCompleted{};
bool world = false; // a save game (AMS), not a hero file
int difficulty = 0; // AMS header +0x84 (g_gameLevel)
int campaign = kCampaignAncaria; // AMS header +0x24 (CampaignSaveMode)
bool multiplayer = false; // AMS header +0x2c
uint64_t savedAt = 0; // AMS header +0x5c SYSTEMTIME, as FILETIME
};
std::optional<SaveInfo> ReadSaveInfo(const Bytes& file);
// Writes `name` as the hero's name (hero header +8, 0x40 UTF-16 characters
// with the terminator), the only place the name is stored; false if the file
// has no hero header or the name is empty or too long.
constexpr size_t kHeroNameMaxChars = 63;
bool SetHeroName(Bytes& file, const std::wstring& name);
// Sets the hero header's story completions of `difficulty` (+0xed..; in a hero
// file they are the hero's, loadHero puts them into cStatsManager); false if
// the file has no hero header.
bool SetStoryCompleted(Bytes& file, int difficulty, uint8_t count);
// The save header's campaign field (+0x24): 2 Ancaria, 3 Underworld (written
// at 0x61a5c5, read back by cEngine::Load 0x61bafd).
uint32_t CampaignSaveMode(int campaign);
// A small save game the game's load window can list: `world`'s header with its
// date, its thumbnail and world time, and `description` (ANSI, up to 63
// characters) as the save's name. The load window lists only saves of the
// running campaign (0x71e2b9): the preview names `campaign`.
Bytes BuildPreview(const Bytes& world, const std::string& description, int campaign);
// Reading and writing whole files (paths in the game's code page).
std::optional<Bytes> ReadFile(const std::string& path);
bool WriteFileReplacing(const std::string& path, const Bytes& bytes);
// A character file: named sections ("meta" JSON, "hero" .pax, "world<d>"
// GAME.PAK). Sections are read from disk on demand; Save writes all of them
// to a temporary file and replaces the old one.
class CharacterFile {
public:
explicit CharacterFile(std::string path) : path_(std::move(path)) {}
// Reads the section table and the meta; false if the file is missing or
// not a character file.
bool Open();
const std::string& Path() const { return path_; }
bool Has(const std::string& section) const;
std::optional<Bytes> Read(const std::string& section) const;
void Set(const std::string& section, Bytes bytes);
void Remove(const std::string& section);
bool Save();
nlohmann::json meta = nlohmann::json::object();
private:
struct Section {
std::string name;
uint32_t offset = 0; // in the file; unused once `data` is set
uint32_t size = 0;
std::optional<Bytes> data; // set or changed since Open
};
std::string path_;
std::vector<Section> sections_;
};
// A world's section: "ancaria<d>" / "underworld<d>", d = g_gameLevel. Sections
// are named, not slotted: more difficulties (or campaigns) are more sections.
std::string WorldSection(int campaign, int difficulty);
} // namespace mods::characters