Characters: one file per character with a world per difficulty
- CharacterNN.sacredK holds the shared hero and one world per difficulty, plus an autosave file; the game works in save\characters\_work - Load window: one row per character, a difficulty dialog on Laden (worlds, newer autosaves, "(neu)" starts a new game with the shared hero), Umbenennen, delete moves the character to characters\deleted - The world keeps its own portals and hero position when the shared hero is put on - K and the Esc menu's "Speichern" save directly (quicksave path) - One-time migration of the old save rows and exported heroes, after a backup - Startup no longer hangs in the game's foreground loop (0x810af0) - Text overrides apply to our own text ids too; shared cUI_Button2 helpers in game/ui/button.h Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5df7efc198
commit
5bccb0734e
16 files changed
+2052
-64
No files matched your search
@@ -0,0 +1,82 @@
|
||||
#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 <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;
|
||||
|
||||
// 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
|
||||
bool world = false; // a save game (AMS), not a hero file
|
||||
int difficulty = 0; // AMS header +0x84 (g_gameLevel)
|
||||
uint32_t mode = 0; // AMS header +0x24: 2 normal, 3 hardcore
|
||||
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);
|
||||
|
||||
// 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.
|
||||
Bytes BuildPreview(const Bytes& world, const std::string& description);
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
std::string WorldSection(int difficulty);
|
||||
|
||||
} // namespace mods::characters
|
||||
Reference in new issue
Block a user