yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_version_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <cstring>
6
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
9
10// For compression (placeholder - would use zlib or similar)
11#include <vector>
12
13#ifdef YAZE_WITH_JSON
14#include "nlohmann/json.hpp"
15#endif
16
17namespace yaze {
18
19namespace net {
20
21namespace {
22
23// Simple hash function (in production, use SHA256)
24std::string ComputeHash(const std::vector<uint8_t>& data) {
25 uint32_t hash = 0;
26 for (size_t i = 0; i < data.size(); ++i) {
27 hash = hash * 31 + data[i];
28 }
29 return absl::StrFormat("%08x", hash);
30}
31
32// Generate unique ID
33std::string GenerateId() {
34 auto now = std::chrono::system_clock::now();
35 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
36 now.time_since_epoch())
37 .count();
38 return absl::StrFormat("snap_%lld", ms);
39}
40
42 return std::chrono::duration_cast<std::chrono::milliseconds>(
43 std::chrono::system_clock::now().time_since_epoch())
44 .count();
45}
46
47} // namespace
48
49// ============================================================================
50// RomVersionManager Implementation
51// ============================================================================
52
54 : rom_(rom), last_backup_time_(0) {}
55
57 // Cleanup if needed
58}
59
60absl::Status RomVersionManager::Initialize(const Config& config) {
61 config_ = config;
62
63 // Create initial snapshot
64 auto initial_result = CreateSnapshot("Initial state", "system", true);
65
66 if (!initial_result.ok()) {
67 return initial_result.status();
68 }
69
70 // Mark as safe point
71 return MarkAsSafePoint(*initial_result);
72}
73
74absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
75 const std::string& description, const std::string& creator,
76 bool is_checkpoint) {
77 if (!rom_ || !rom_->is_loaded()) {
78 return absl::FailedPreconditionError("ROM not loaded");
79 }
80
81 // Get ROM data
82 std::vector<uint8_t> rom_data(rom_->size());
83 std::memcpy(rom_data.data(), rom_->data(), rom_->size());
84
85 // Create snapshot
86 RomSnapshot snapshot;
87 snapshot.snapshot_id = GenerateId();
88 snapshot.description = description;
89 snapshot.timestamp = GetCurrentTimestamp();
90 snapshot.rom_hash = ComputeHash(rom_data);
91 snapshot.creator = creator;
92 snapshot.is_checkpoint = is_checkpoint;
93 snapshot.is_safe_point = false;
94
95 // Compress if enabled
97 snapshot.rom_data = CompressData(rom_data);
98 snapshot.compressed_size = snapshot.rom_data.size();
99 } else {
100 snapshot.rom_data = std::move(rom_data);
101 snapshot.compressed_size = snapshot.rom_data.size();
102 }
103
104#ifdef YAZE_WITH_JSON
105 snapshot.metadata = nlohmann::json::object();
106 snapshot.metadata["size"] = rom_->size();
107 snapshot.metadata["auto_backup"] = !is_checkpoint;
108#endif
109
110 // Store snapshot
111 snapshots_[snapshot.snapshot_id] = std::move(snapshot);
112 last_known_hash_ = snapshots_[snapshot.snapshot_id].rom_hash;
113
114 // Cleanup if needed
115 if (snapshots_.size() > config_.max_snapshots) {
117 }
118
119 return snapshots_[snapshot.snapshot_id].snapshot_id;
120}
121
123 const std::string& snapshot_id) {
124 auto it = snapshots_.find(snapshot_id);
125 if (it == snapshots_.end()) {
126 return absl::NotFoundError(
127 absl::StrCat("Snapshot not found: ", snapshot_id));
128 }
129
130 if (!rom_ || !rom_->is_loaded()) {
131 return absl::FailedPreconditionError("ROM not loaded");
132 }
133
134 const RomSnapshot& snapshot = it->second;
135
136 // Decompress if needed
137 std::vector<uint8_t> rom_data;
139 rom_data = DecompressData(snapshot.rom_data);
140 } else {
141 rom_data = snapshot.rom_data;
142 }
143
144 // Verify size matches
145 if (rom_data.size() != rom_->size()) {
146 return absl::DataLossError("Snapshot size mismatch");
147 }
148
149 // Create backup before restore
150 auto backup_result = CreateSnapshot("Pre-restore backup", "system", false);
151
152 if (!backup_result.ok()) {
153 return absl::InternalError("Failed to create pre-restore backup");
154 }
155
156 // Restore ROM data
157 std::memcpy(rom_->mutable_data(), rom_data.data(), rom_data.size());
158
159 last_known_hash_ = snapshot.rom_hash;
160
161 return absl::OkStatus();
162}
163
165 const std::string& snapshot_id) {
166 auto it = snapshots_.find(snapshot_id);
167 if (it == snapshots_.end()) {
168 return absl::NotFoundError("Snapshot not found");
169 }
170
171 it->second.is_safe_point = true;
172 return absl::OkStatus();
173}
174
175std::vector<RomSnapshot> RomVersionManager::GetSnapshots(
176 bool safe_points_only) const {
177 std::vector<RomSnapshot> result;
178
179 for (const auto& [id, snapshot] : snapshots_) {
180 if (!safe_points_only || snapshot.is_safe_point) {
181 result.push_back(snapshot);
182 }
183 }
184
185 // Sort by timestamp (newest first)
186 std::sort(result.begin(), result.end(),
187 [](const RomSnapshot& a, const RomSnapshot& b) {
188 return a.timestamp > b.timestamp;
189 });
190
191 return result;
192}
193
194absl::StatusOr<RomSnapshot> RomVersionManager::GetSnapshot(
195 const std::string& snapshot_id) const {
196 auto it = snapshots_.find(snapshot_id);
197 if (it == snapshots_.end()) {
198 return absl::NotFoundError("Snapshot not found");
199 }
200 return it->second;
201}
202
203absl::Status RomVersionManager::DeleteSnapshot(const std::string& snapshot_id) {
204 auto it = snapshots_.find(snapshot_id);
205 if (it == snapshots_.end()) {
206 return absl::NotFoundError("Snapshot not found");
207 }
208
209 // Don't allow deleting safe points
210 if (it->second.is_safe_point) {
211 return absl::FailedPreconditionError("Cannot delete safe point");
212 }
213
214 snapshots_.erase(it);
215 return absl::OkStatus();
216}
217
220 return false;
221 }
222
223 if (!rom_ || !rom_->is_loaded()) {
224 return absl::FailedPreconditionError("ROM not loaded");
225 }
226
227 // Compute current hash
228 std::vector<uint8_t> current_data(rom_->size());
229 std::memcpy(current_data.data(), rom_->data(), rom_->size());
230 std::string current_hash = ComputeHash(current_data);
231
232 // Basic integrity checks
233 auto integrity_status = ValidateRomIntegrity();
234 if (!integrity_status.ok()) {
235 return true; // Corruption detected
236 }
237
238 // Check against last known good hash (if modified unexpectedly)
239 if (!last_known_hash_.empty() && current_hash != last_known_hash_) {
240 // ROM changed without going through version manager
241 // This might be intentional, so just flag it
242 return false;
243 }
244
245 return false;
246}
247
249 // Find most recent safe point
250 auto snapshots = GetSnapshots(true);
251 if (snapshots.empty()) {
252 return absl::NotFoundError("No safe points available for recovery");
253 }
254
255 // Restore from most recent safe point
256 return RestoreSnapshot(snapshots[0].snapshot_id);
257}
258
260 if (!rom_ || !rom_->is_loaded()) {
261 return "";
262 }
263
264 std::vector<uint8_t> data(rom_->size());
265 std::memcpy(data.data(), rom_->data(), rom_->size());
266 return ComputeHash(data);
267}
268
270 // Keep safe points and checkpoints
271 // Remove oldest auto-backups first
272
273 std::vector<std::pair<int64_t, std::string>> auto_backups;
274 for (const auto& [id, snapshot] : snapshots_) {
275 if (!snapshot.is_safe_point && !snapshot.is_checkpoint) {
276 auto_backups.push_back({snapshot.timestamp, id});
277 }
278 }
279
280 // Sort by timestamp (oldest first)
281 std::sort(auto_backups.begin(), auto_backups.end());
282
283 // Delete oldest until within limits
284 while (snapshots_.size() > config_.max_snapshots && !auto_backups.empty()) {
285 snapshots_.erase(auto_backups.front().second);
286 auto_backups.erase(auto_backups.begin());
287 }
288
289 // Check storage limit
290 while (GetTotalStorageUsed() > config_.max_storage_mb * 1024 * 1024 &&
291 !auto_backups.empty()) {
292 snapshots_.erase(auto_backups.front().second);
293 auto_backups.erase(auto_backups.begin());
294 }
295
296 return absl::OkStatus();
297}
298
300 Stats stats = {};
301 stats.total_snapshots = snapshots_.size();
302
303 for (const auto& [id, snapshot] : snapshots_) {
304 if (snapshot.is_safe_point)
305 stats.safe_points++;
306 if (snapshot.is_checkpoint)
307 stats.manual_checkpoints++;
308 if (!snapshot.is_checkpoint)
309 stats.auto_backups++;
310 stats.total_storage_bytes += snapshot.compressed_size;
311
312 if (stats.oldest_snapshot_timestamp == 0 ||
313 snapshot.timestamp < stats.oldest_snapshot_timestamp) {
314 stats.oldest_snapshot_timestamp = snapshot.timestamp;
315 }
316
317 if (snapshot.timestamp > stats.newest_snapshot_timestamp) {
318 stats.newest_snapshot_timestamp = snapshot.timestamp;
319 }
320 }
321
322 return stats;
323}
324
325// Private helper methods
326
328 if (!rom_ || !rom_->is_loaded()) {
329 return "";
330 }
331
332 std::vector<uint8_t> data(rom_->size());
333 std::memcpy(data.data(), rom_->data(), rom_->size());
334 return ComputeHash(data);
335}
336
338 const std::vector<uint8_t>& data) const {
339 // Placeholder: In production, use zlib or similar
340 // For now, just return the data as-is
341 return data;
342}
343
345 const std::vector<uint8_t>& compressed) const {
346 // Placeholder: In production, use zlib or similar
347 return compressed;
348}
349
351 if (!rom_ || !rom_->is_loaded()) {
352 return absl::FailedPreconditionError("ROM not loaded");
353 }
354
355 // Basic checks
356 if (rom_->size() == 0) {
357 return absl::DataLossError("ROM size is zero");
358 }
359
360 // Check for valid SNES header
361 // (This is a simplified check - real validation would be more thorough)
362 if (rom_->size() < 0x8000) {
363 return absl::DataLossError("ROM too small to be valid");
364 }
365
366 return absl::OkStatus();
367}
368
370 size_t total = 0;
371 for (const auto& [id, snapshot] : snapshots_) {
372 total += snapshot.compressed_size;
373 }
374 return total;
375}
376
377// ============================================================================
378// ProposalApprovalManager Implementation
379// ============================================================================
380
382 : version_mgr_(version_mgr), mode_(ApprovalMode::kHostOnly) {}
383
387
388void ProposalApprovalManager::SetHost(const std::string& host_username) {
389 host_username_ = host_username;
390}
391
393 const std::string& proposal_id, const std::string& sender,
394 const std::string& description, const nlohmann::json& proposal_data) {
395 ApprovalStatus status;
396 status.proposal_id = proposal_id;
397 status.status = "pending";
398 status.created_at = GetCurrentTimestamp();
399 status.decided_at = 0;
400
401 // Create snapshot before potential application
402 auto snapshot_result = version_mgr_->CreateSnapshot(
403 absl::StrCat("Before proposal: ", description), sender, false);
404
405 if (!snapshot_result.ok()) {
406 return snapshot_result.status();
407 }
408
409 status.snapshot_before = *snapshot_result;
410
411 proposals_[proposal_id] = status;
412
413 return absl::OkStatus();
414}
415
417 const std::string& proposal_id, const std::string& username,
418 bool approved) {
419 auto it = proposals_.find(proposal_id);
420 if (it == proposals_.end()) {
421 return absl::NotFoundError("Proposal not found");
422 }
423
424 ApprovalStatus& status = it->second;
425
426 if (status.status != "pending") {
427 return absl::FailedPreconditionError("Proposal already decided");
428 }
429
430 // Record vote
431 status.votes[username] = approved;
432
433 // Check if decision can be made
434 if (CheckApprovalThreshold(status)) {
435 status.status = "approved";
436 status.decided_at = GetCurrentTimestamp();
437 } else {
438 // Check if rejection threshold reached
439 size_t rejection_count = 0;
440 for (const auto& [user, vote] : status.votes) {
441 if (!vote)
442 rejection_count++;
443 }
444
445 // If host rejected (in host-only mode), reject immediately
446 if (mode_ == ApprovalMode::kHostOnly && username == host_username_ &&
447 !approved) {
448 status.status = "rejected";
449 status.decided_at = GetCurrentTimestamp();
450 }
451 }
452
453 return absl::OkStatus();
454}
455
457 const ApprovalStatus& status) const {
458 switch (mode_) {
460 // Only host vote matters
461 if (status.votes.find(host_username_) != status.votes.end()) {
462 return status.votes.at(host_username_);
463 }
464 return false;
465
467 size_t approval_count = 0;
468 for (const auto& [user, approved] : status.votes) {
469 if (approved)
470 approval_count++;
471 }
472 return approval_count > participants_.size() / 2;
473 }
474
476 if (status.votes.size() < participants_.size()) {
477 return false; // Not everyone voted yet
478 }
479 for (const auto& [user, approved] : status.votes) {
480 if (!approved)
481 return false;
482 }
483 return true;
484 }
485
487 return true;
488 }
489
490 return false;
491}
492
494 const std::string& proposal_id) const {
495 auto it = proposals_.find(proposal_id);
496 if (it == proposals_.end()) {
497 return false;
498 }
499 return it->second.status == "approved";
500}
501
502std::vector<ProposalApprovalManager::ApprovalStatus>
504 std::vector<ApprovalStatus> pending;
505 for (const auto& [id, status] : proposals_) {
506 if (status.status == "pending") {
507 pending.push_back(status);
508 }
509 }
510 return pending;
511}
512
513absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
515 const std::string& proposal_id) const {
516 auto it = proposals_.find(proposal_id);
517 if (it == proposals_.end()) {
518 return absl::NotFoundError("Proposal not found");
519 }
520 return it->second;
521}
522
523} // namespace net
524
525} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto mutable_data()
Definition rom.h:136
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
std::vector< std::string > participants_
void SetHost(const std::string &host_username)
absl::StatusOr< ApprovalStatus > GetProposalStatus(const std::string &proposal_id) const
absl::Status VoteOnProposal(const std::string &proposal_id, const std::string &username, bool approved)
bool IsProposalApproved(const std::string &proposal_id) const
absl::Status SubmitProposal(const std::string &proposal_id, const std::string &sender, const std::string &description, const nlohmann::json &proposal_data)
std::vector< ApprovalStatus > GetPendingProposals() const
bool CheckApprovalThreshold(const ApprovalStatus &status) const
ProposalApprovalManager(RomVersionManager *version_mgr)
std::map< std::string, ApprovalStatus > proposals_
Manages ROM versioning, snapshots, and rollback capabilities.
std::vector< uint8_t > DecompressData(const std::vector< uint8_t > &compressed) const
absl::StatusOr< std::string > CreateSnapshot(const std::string &description, const std::string &creator, bool is_checkpoint=false)
absl::StatusOr< RomSnapshot > GetSnapshot(const std::string &snapshot_id) const
std::map< std::string, RomSnapshot > snapshots_
absl::Status ValidateRomIntegrity() const
absl::Status Initialize(const Config &config)
absl::StatusOr< bool > DetectCorruption()
absl::Status RestoreSnapshot(const std::string &snapshot_id)
absl::Status MarkAsSafePoint(const std::string &snapshot_id)
std::vector< uint8_t > CompressData(const std::vector< uint8_t > &data) const
absl::Status DeleteSnapshot(const std::string &snapshot_id)
std::vector< RomSnapshot > GetSnapshots(bool safe_points_only=false) const
std::string ComputeHash(const std::vector< uint8_t > &data)
Represents a versioned snapshot of ROM state.
std::vector< uint8_t > rom_data