feat(common): read the memory text the client sends at login

The login request's memoryStats is a run of short parts with no separators
(process working set and private bytes, memory load, physical memory, commit
limit, the 32-bit client's own address space, peaks). ParseMemoryStats splits
it into numbers; a text cut short keeps the parts before the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 18:40:22 -05:00
parent 1e5660512e
commit b67e0ba9ba
5 changed files with 159 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
set(DCOMMON_SOURCES
"AMFDeserialize.cpp"
"ClientSysInfo.cpp"
"AmfSerialize.cpp"
"BinaryIO.cpp"
"dConfig.cpp"

61
dCommon/ClientSysInfo.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "ClientSysInfo.h"
#include <array>
#include <cctype>
#include <charconv>
namespace {
struct Reader {
std::string_view text;
size_t pos{};
void SkipSpace() {
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos]))) pos++;
}
bool Literal(std::string_view literal) {
SkipSpace();
if (text.substr(pos, literal.size()) != literal) return false;
pos += literal.size();
return true;
}
std::optional<int64_t> Number() {
SkipSpace();
int64_t value{};
const auto* begin = text.data() + pos;
const auto [end, error] = std::from_chars(begin, text.data() + text.size(), value);
if (error != std::errc{} || end == begin) return std::nullopt;
pos += static_cast<size_t>(end - begin);
return value;
}
};
}
ClientSysInfo::MemoryStats ClientSysInfo::ParseMemoryStats(const std::string_view text) {
MemoryStats stats;
// Each part: text before the number, text after it, and where the number goes
struct Part { std::string_view prefix; std::string_view suffix; std::optional<int64_t> MemoryStats::* field; };
const std::array<Part, 11> parts{ {
{ "", "p,", &MemoryStats::workingSetBytes },
{ "", "vbytes.", &MemoryStats::pagefileUsageBytes },
{ "", "n-use.", &MemoryStats::memoryLoadPercent },
{ "", "TKb-pmem.", &MemoryStats::totalPhysKb },
{ "", "FKb pmem.", &MemoryStats::availPhysKb },
{ "", "TKb pfile.", &MemoryStats::totalPageFileKb },
{ "", "FKb pfile.", &MemoryStats::availPageFileKb },
{ "", "TKbytes vmem.", &MemoryStats::totalVirtualKb },
{ "", "FKb vmem.", &MemoryStats::availVirtualKb },
{ "P", "p,", &MemoryStats::peakWorkingSetBytes },
{ "", "v.", &MemoryStats::peakPagefileUsageBytes },
} };
Reader reader{ text };
for (const auto& part : parts) {
if (!part.prefix.empty() && !reader.Literal(part.prefix)) return stats;
const auto value = reader.Number();
if (!value || !reader.Literal(part.suffix)) return stats;
stats.*part.field = *value;
}
stats.complete = true;
return stats;
}

39
dCommon/ClientSysInfo.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string_view>
/**
* The system description the client sends in its login request, as reported by the client. It comes from old Windows
* calls and describes what Windows (or Wine) tells a 32-bit program without a compatibility manifest, which is not
* necessarily the player's real hardware. docs/Dashboard.md (Client system info) says what each field means.
*/
namespace ClientSysInfo {
/**
* The login request's memoryStats text, split into numbers. The client writes these parts one after another with no
* separator (numbers are right-aligned to 7 characters):
* "<ws> p,<pf> vbytes." "<load> n-use." "<kb> TKb-pmem." "<kb> FKb pmem." "<kb> TKb pfile." "<kb> FKb pfile."
* "<kb> TKbytes vmem. \n" "<kb> FKb vmem." "P <peak ws> p,<peak pf> v."
* The client cuts the text at 255 characters, so the last parts can be missing; a part that doesn't match stops the
* parse and leaves it and everything after it empty.
*/
struct MemoryStats {
std::optional<int64_t> workingSetBytes; // the client process's working set at login (bytes)
std::optional<int64_t> pagefileUsageBytes; // the client process's private (commit) bytes at login
std::optional<int64_t> memoryLoadPercent; // how much of the system's memory Windows says is in use
std::optional<int64_t> totalPhysKb; // physical memory Windows reports (KB)
std::optional<int64_t> availPhysKb; // free physical memory at login (KB)
std::optional<int64_t> totalPageFileKb; // commit limit: physical memory plus page files (KB)
std::optional<int64_t> availPageFileKb; // free commit at login (KB)
std::optional<int64_t> totalVirtualKb; // the 32-bit client's own address space, not the system's (KB)
std::optional<int64_t> availVirtualKb; // free address space in the client (KB)
std::optional<int64_t> peakWorkingSetBytes; // the client process's peak working set (bytes)
std::optional<int64_t> peakPagefileUsageBytes; // the client process's peak private bytes
// Every part was read
bool complete{};
};
MemoryStats ParseMemoryStats(std::string_view text);
}