GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 88.9% 8 / 0 / 9
Functions: 100.0% 1 / 0 / 1
Branches: 75.0% 3 / 0 / 4

include/DetourModKit/drift_manifest.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_DRIFT_MANIFEST_HPP
2 #define DETOURMODKIT_DRIFT_MANIFEST_HPP
3
4 /**
5 * @file drift_manifest.hpp
6 * @brief Durable serialization of self-heal drift reports (@ref DetourModKit::Rtti::DriftEntry).
7 * @details Lets a consumer persist a @ref DetourModKit::Rtti::heal_report across game versions and diff the saved
8 * manifests to see which offsets moved between patches, instead of only logging the live telemetry once per
9 * run.
10 */
11
12 #include "DetourModKit/rtti_dissect.hpp"
13
14 #include <cstddef>
15 #include <cstdint>
16 #include <expected>
17 #include <span>
18 #include <string>
19 #include <string_view>
20 #include <vector>
21
22 namespace DetourModKit
23 {
24 namespace Rtti
25 {
26 /**
27 * @struct DriftRecord
28 * @brief An owning, parsed drift entry read back from a manifest.
29 * @details The persisted counterpart to @ref DriftEntry. Unlike DriftEntry, whose @c name aliases caller
30 * storage, DriftRecord owns its name so it stays valid after the manifest text or file buffer is gone.
31 */
32 struct DriftRecord
33 {
34 /// The landmark name (owned).
35 std::string name;
36 /// Last-known offset recorded at write time.
37 std::ptrdiff_t nominal_offset = 0;
38 /// Resolved offset (meaningful only when @ref ok).
39 std::ptrdiff_t healed_offset = 0;
40 /// healed_offset - nominal_offset (meaningful only when @ref ok).
41 std::ptrdiff_t delta = 0;
42 /// Whether the landmark healed.
43 bool ok = false;
44 /// Failure reason (meaningful only when @ref ok is false).
45 HealError error{};
46 };
47
48 /**
49 * @enum ManifestError
50 * @brief Why reading or parsing a drift manifest failed. Fails closed: no partial result.
51 */
52 enum class ManifestError : std::uint8_t
53 {
54 /// The first non-blank line was not the manifest header (file present but corrupt).
55 MissingHeader,
56 /// A record line had the wrong field count or an unparseable field (file present but corrupt).
57 MalformedLine,
58 /// The file could not be opened (missing, locked, permission denied, or not a regular file).
59 FileOpenFailed
60 };
61
62 /**
63 * @brief Human-readable mapping for @ref ManifestError.
64 * @param error The error code.
65 * @return A string view describing the error.
66 */
67 9 [[nodiscard]] constexpr std::string_view manifest_error_to_string(ManifestError error) noexcept
68 {
69
3/4
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 3 times.
✓ Branch 2 → 5 taken 3 times.
✗ Branch 2 → 6 not taken.
9 switch (error)
70 {
71 3 case ManifestError::MissingHeader:
72 3 return "Manifest header line is missing or wrong";
73 3 case ManifestError::MalformedLine:
74 3 return "A manifest record line is malformed";
75 3 case ManifestError::FileOpenFailed:
76 3 return "Manifest file could not be opened";
77 }
78 return "Unknown manifest error";
79 }
80
81 /**
82 * @brief Serializes a drift report to a durable, line-oriented manifest.
83 * @details Emits a versioned header line followed by one tab-separated line per entry (name, nominal_offset,
84 * healed_offset, delta, ok, error). The error is written as a stable token, not the human-readable
85 * @ref heal_error_to_string text, so the manifest round-trips. Names are assumed free of tab and
86 * newline (MSVC mangled type names are).
87 * @param entries The drift entries to serialize (e.g. from @ref heal_report).
88 * @return The manifest text.
89 */
90 [[nodiscard]] std::string serialize_drift_report(std::span<const DriftEntry> entries);
91
92 /**
93 * @brief Parses a drift manifest produced by @ref serialize_drift_report.
94 * @details Tolerates blank lines and trailing carriage returns (CRLF input). Fails closed on a missing header
95 * or any malformed record line.
96 * @param text The manifest text.
97 * @return The parsed records, or a @ref ManifestError.
98 */
99 [[nodiscard]] std::expected<std::vector<DriftRecord>, ManifestError> parse_drift_report(std::string_view text);
100
101 /**
102 * @brief Writes a drift report to a file via @ref serialize_drift_report.
103 * @param path Destination file path (UTF-8).
104 * @param entries The drift entries to serialize.
105 * @return true on success, false if the file could not be opened or written.
106 * @note The write is not atomic: it truncates @p path in place, so a crash or power loss mid-write can leave a
107 * partial manifest. That is acceptable here because the manifest is a regenerable diagnostic/diff
108 * artifact (offsets are re-healed every session, never loaded as load-bearing state); a torn file is
109 * reported as MalformedLine / MissingHeader on the next read and overwritten. Do not route load-bearing
110 * data through this path without first making the write atomic (temp file + replace).
111 */
112 [[nodiscard]] bool write_drift_report_to_file(const std::string &path, std::span<const DriftEntry> entries);
113
114 /**
115 * @brief Reads and parses a drift manifest file.
116 * @param path Source file path (UTF-8).
117 * @return The parsed records, or a @ref ManifestError: @ref ManifestError::FileOpenFailed when the file cannot
118 * be opened, or a parse error (@ref ManifestError::MissingHeader / @ref ManifestError::MalformedLine)
119 * when the file is present but its contents are corrupt. An opened-but-empty file reports
120 * MissingHeader.
121 */
122 [[nodiscard]] std::expected<std::vector<DriftRecord>, ManifestError>
123 read_drift_report_from_file(const std::string &path);
124 } // namespace Rtti
125 } // namespace DetourModKit
126
127 #endif // DETOURMODKIT_DRIFT_MANIFEST_HPP
128