yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
write_fence.h
Go to the documentation of this file.
1#ifndef YAZE_ROM_WRITE_FENCE_H
2#define YAZE_ROM_WRITE_FENCE_H
3
4#include <algorithm>
5#include <cstdint>
6#include <limits>
7#include <string>
8#include <string_view>
9#include <utility>
10#include <vector>
11
12#include "absl/status/status.h"
13#include "absl/strings/str_format.h"
14#include "rom/rom.h"
15
16namespace yaze::rom {
17
18// Half-open PC offset range [start, end).
19struct WriteRange {
20 uint32_t start = 0;
21 uint32_t end = 0;
22 std::string label;
23};
24
25// A strict allow-list for ROM writes.
26//
27// When a WriteFence is active on a Rom (via ScopedWriteFence), Rom::Write* calls
28// will be rejected unless the full write range is contained within one of the
29// allowed ranges.
31 public:
32 absl::Status Allow(uint32_t start, uint32_t end, std::string_view label) {
33 if (start >= end) {
34 return absl::InvalidArgumentError(
35 absl::StrFormat("WriteFence: invalid range [%u, %u)", start, end));
36 }
37 WriteRange r;
38 r.start = start;
39 r.end = end;
40 r.label = std::string(label);
41
42 for (const auto& existing : allowed_) {
43 if (RangesOverlap(r.start, r.end, existing.start, existing.end)) {
44 return absl::AlreadyExistsError(absl::StrFormat(
45 "WriteFence: range [%u, %u) overlaps existing [%u, %u) (%s)",
46 r.start, r.end, existing.start, existing.end,
47 existing.label.c_str()));
48 }
49 }
50
51 allowed_.push_back(std::move(r));
52 std::sort(allowed_.begin(), allowed_.end(),
53 [](const WriteRange& a, const WriteRange& b) {
54 return a.start < b.start;
55 });
56 return absl::OkStatus();
57 }
58
59 absl::Status Check(uint32_t start, uint32_t size,
60 std::string_view op) const {
61 if (size == 0) {
62 return absl::OkStatus();
63 }
64 const uint64_t end64 = static_cast<uint64_t>(start) + size;
65 if (end64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) +
66 1ULL) {
67 return absl::OutOfRangeError(
68 "WriteFence: write range overflows uint32");
69 }
70 const uint32_t end = static_cast<uint32_t>(end64);
71 for (const auto& a : allowed_) {
72 if (start >= a.start && end <= a.end) {
73 return absl::OkStatus();
74 }
75 }
76 return absl::PermissionDeniedError(absl::StrFormat(
77 "ROM write fence blocked %s at [0x%06X, 0x%06X)",
78 std::string(op).c_str(), start, end));
79 }
80
81 void RecordWrite(uint32_t start, uint32_t size) {
82 if (size == 0) {
83 return;
84 }
85 const uint64_t end64 = static_cast<uint64_t>(start) + size;
86 if (end64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) +
87 1ULL) {
88 // Can't represent end in uint32; ignore for recording.
89 return;
90 }
91 const uint32_t end = static_cast<uint32_t>(end64);
92 AddWrittenRange(start, end);
93 }
94
95 const std::vector<WriteRange>& allowed_ranges() const { return allowed_; }
96 const std::vector<std::pair<uint32_t, uint32_t>>& written_ranges() const {
97 return written_;
98 }
99
100 void ClearWritten() { written_.clear(); }
101
102 private:
103 static bool RangesOverlap(uint32_t a_start, uint32_t a_end, uint32_t b_start,
104 uint32_t b_end) {
105 return a_start < b_end && b_start < a_end;
106 }
107
108 void AddWrittenRange(uint32_t start, uint32_t end) {
109 if (start >= end) {
110 return;
111 }
112
113 // Insert and merge into `written_` (kept sorted by start, non-overlapping).
114 std::pair<uint32_t, uint32_t> r{start, end};
115 auto it = std::lower_bound(
116 written_.begin(), written_.end(), r,
117 [](const auto& a, const auto& b) { return a.first < b.first; });
118 written_.insert(it, r);
119
120 // Merge pass (written_ is typically tiny).
121 std::vector<std::pair<uint32_t, uint32_t>> merged;
122 merged.reserve(written_.size());
123 for (const auto& cur : written_) {
124 if (merged.empty()) {
125 merged.push_back(cur);
126 continue;
127 }
128 auto& back = merged.back();
129 if (cur.first <= back.second) {
130 back.second = std::max(back.second, cur.second);
131 } else {
132 merged.push_back(cur);
133 }
134 }
135 written_.swap(merged);
136 }
137
138 std::vector<WriteRange> allowed_;
139 std::vector<std::pair<uint32_t, uint32_t>> written_;
140};
141
142// Scoped activation of a WriteFence. Fences are stacked; writes must be allowed
143// by all fences currently active on the Rom (logical AND).
145 public:
146 ScopedWriteFence(Rom* rom, WriteFence* fence) : rom_(rom), fence_(fence) {
147 if (rom_ && fence_) {
149 }
150 }
151
153 if (rom_ && fence_) {
155 }
156 }
157
160
161 private:
162 Rom* rom_ = nullptr;
163 WriteFence* fence_ = nullptr;
164};
165
166} // namespace yaze::rom
167
168#endif // YAZE_ROM_WRITE_FENCE_H
169
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
void PushWriteFence(rom::WriteFence *fence)
Definition rom.cc:380
void PopWriteFence(rom::WriteFence *fence)
Definition rom.cc:387
ScopedWriteFence(const ScopedWriteFence &)=delete
ScopedWriteFence & operator=(const ScopedWriteFence &)=delete
ScopedWriteFence(Rom *rom, WriteFence *fence)
void RecordWrite(uint32_t start, uint32_t size)
Definition write_fence.h:81
std::vector< std::pair< uint32_t, uint32_t > > written_
std::vector< WriteRange > allowed_
static bool RangesOverlap(uint32_t a_start, uint32_t a_end, uint32_t b_start, uint32_t b_end)
const std::vector< std::pair< uint32_t, uint32_t > > & written_ranges() const
Definition write_fence.h:96
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
Definition write_fence.h:32
const std::vector< WriteRange > & allowed_ranges() const
Definition write_fence.h:95
absl::Status Check(uint32_t start, uint32_t size, std::string_view op) const
Definition write_fence.h:59
void AddWrittenRange(uint32_t start, uint32_t end)