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 auto restore_status = rom_->WriteVector(0, std::move(rom_data));
158 if (!restore_status.ok()) {
159 return restore_status;
160 }
161
162 last_known_hash_ = snapshot.rom_hash;
163
164 return absl::OkStatus();
165}
166
168 const std::string& snapshot_id) {
169 auto it = snapshots_.find(snapshot_id);
170 if (it == snapshots_.end()) {
171 return absl::NotFoundError("Snapshot not found");
172 }
173
174 it->second.is_safe_point = true;
175 return absl::OkStatus();
176}
177
178std::vector<RomSnapshot> RomVersionManager::GetSnapshots(
179 bool safe_points_only) const {
180 std::vector<RomSnapshot> result;
181
182 for (const auto& [id, snapshot] : snapshots_) {
183 if (!safe_points_only || snapshot.is_safe_point) {
184 result.push_back(snapshot);
185 }
186 }
187
188 // Sort by timestamp (newest first)
189 std::sort(result.begin(), result.end(),
190 [](const RomSnapshot& a, const RomSnapshot& b) {
191 return a.timestamp > b.timestamp;
192 });
193
194 return result;
195}
196
197absl::StatusOr<RomSnapshot> RomVersionManager::GetSnapshot(
198 const std::string& snapshot_id) const {
199 auto it = snapshots_.find(snapshot_id);
200 if (it == snapshots_.end()) {
201 return absl::NotFoundError("Snapshot not found");
202 }
203 return it->second;
204}
205
206absl::Status RomVersionManager::DeleteSnapshot(const std::string& snapshot_id) {
207 auto it = snapshots_.find(snapshot_id);
208 if (it == snapshots_.end()) {
209 return absl::NotFoundError("Snapshot not found");
210 }
211
212 // Don't allow deleting safe points
213 if (it->second.is_safe_point) {
214 return absl::FailedPreconditionError("Cannot delete safe point");
215 }
216
217 snapshots_.erase(it);
218 return absl::OkStatus();
219}
220
223 return false;
224 }
225
226 if (!rom_ || !rom_->is_loaded()) {
227 return absl::FailedPreconditionError("ROM not loaded");
228 }
229
230 // Compute current hash
231 std::vector<uint8_t> current_data(rom_->size());
232 std::memcpy(current_data.data(), rom_->data(), rom_->size());
233 std::string current_hash = ComputeHash(current_data);
234
235 // Basic integrity checks
236 auto integrity_status = ValidateRomIntegrity();
237 if (!integrity_status.ok()) {
238 return true; // Corruption detected
239 }
240
241 // Check against last known good hash (if modified unexpectedly)
242 if (!last_known_hash_.empty() && current_hash != last_known_hash_) {
243 // ROM changed without going through version manager
244 // This might be intentional, so just flag it
245 return false;
246 }
247
248 return false;
249}
250
252 // Find most recent safe point
253 auto snapshots = GetSnapshots(true);
254 if (snapshots.empty()) {
255 return absl::NotFoundError("No safe points available for recovery");
256 }
257
258 // Restore from most recent safe point
259 return RestoreSnapshot(snapshots[0].snapshot_id);
260}
261
263 if (!rom_ || !rom_->is_loaded()) {
264 return "";
265 }
266
267 std::vector<uint8_t> data(rom_->size());
268 std::memcpy(data.data(), rom_->data(), rom_->size());
269 return ComputeHash(data);
270}
271
273 // Keep safe points and checkpoints
274 // Remove oldest auto-backups first
275
276 std::vector<std::pair<int64_t, std::string>> auto_backups;
277 for (const auto& [id, snapshot] : snapshots_) {
278 if (!snapshot.is_safe_point && !snapshot.is_checkpoint) {
279 auto_backups.push_back({snapshot.timestamp, id});
280 }
281 }
282
283 // Sort by timestamp (oldest first)
284 std::sort(auto_backups.begin(), auto_backups.end());
285
286 // Delete oldest until within limits
287 while (snapshots_.size() > config_.max_snapshots && !auto_backups.empty()) {
288 snapshots_.erase(auto_backups.front().second);
289 auto_backups.erase(auto_backups.begin());
290 }
291
292 // Check storage limit
293 while (GetTotalStorageUsed() > config_.max_storage_mb * 1024 * 1024 &&
294 !auto_backups.empty()) {
295 snapshots_.erase(auto_backups.front().second);
296 auto_backups.erase(auto_backups.begin());
297 }
298
299 return absl::OkStatus();
300}
301
303 Stats stats = {};
304 stats.total_snapshots = snapshots_.size();
305
306 for (const auto& [id, snapshot] : snapshots_) {
307 if (snapshot.is_safe_point)
308 stats.safe_points++;
309 if (snapshot.is_checkpoint)
310 stats.manual_checkpoints++;
311 if (!snapshot.is_checkpoint)
312 stats.auto_backups++;
313 stats.total_storage_bytes += snapshot.compressed_size;
314
315 if (stats.oldest_snapshot_timestamp == 0 ||
316 snapshot.timestamp < stats.oldest_snapshot_timestamp) {
317 stats.oldest_snapshot_timestamp = snapshot.timestamp;
318 }
319
320 if (snapshot.timestamp > stats.newest_snapshot_timestamp) {
321 stats.newest_snapshot_timestamp = snapshot.timestamp;
322 }
323 }
324
325 return stats;
326}
327
328// Private helper methods
329
331 if (!rom_ || !rom_->is_loaded()) {
332 return "";
333 }
334
335 std::vector<uint8_t> data(rom_->size());
336 std::memcpy(data.data(), rom_->data(), rom_->size());
337 return ComputeHash(data);
338}
339
341 const std::vector<uint8_t>& data) const {
342 // Placeholder: In production, use zlib or similar
343 // For now, just return the data as-is
344 return data;
345}
346
348 const std::vector<uint8_t>& compressed) const {
349 // Placeholder: In production, use zlib or similar
350 return compressed;
351}
352
354 if (!rom_ || !rom_->is_loaded()) {
355 return absl::FailedPreconditionError("ROM not loaded");
356 }
357
358 // Basic checks
359 if (rom_->size() == 0) {
360 return absl::DataLossError("ROM size is zero");
361 }
362
363 // Check for valid SNES header
364 // (This is a simplified check - real validation would be more thorough)
365 if (rom_->size() < 0x8000) {
366 return absl::DataLossError("ROM too small to be valid");
367 }
368
369 return absl::OkStatus();
370}
371
373 size_t total = 0;
374 for (const auto& [id, snapshot] : snapshots_) {
375 total += snapshot.compressed_size;
376 }
377 return total;
378}
379
380// ============================================================================
381// ProposalApprovalManager Implementation
382// ============================================================================
383
385 : version_mgr_(version_mgr), mode_(ApprovalMode::kHostOnly) {}
386
390
391void ProposalApprovalManager::SetHost(const std::string& host_username) {
392 host_username_ = host_username;
393}
394
396 const std::string& proposal_id, const std::string& sender,
397 const std::string& description, const nlohmann::json& proposal_data) {
398 ApprovalStatus status;
399 status.proposal_id = proposal_id;
400 status.status = "pending";
401 status.created_at = GetCurrentTimestamp();
402 status.decided_at = 0;
403
404 // Create snapshot before potential application
405 auto snapshot_result = version_mgr_->CreateSnapshot(
406 absl::StrCat("Before proposal: ", description), sender, false);
407
408 if (!snapshot_result.ok()) {
409 return snapshot_result.status();
410 }
411
412 status.snapshot_before = *snapshot_result;
413
414 proposals_[proposal_id] = status;
415
416 return absl::OkStatus();
417}
418
420 const std::string& proposal_id, const std::string& username,
421 bool approved) {
422 auto it = proposals_.find(proposal_id);
423 if (it == proposals_.end()) {
424 return absl::NotFoundError("Proposal not found");
425 }
426
427 ApprovalStatus& status = it->second;
428
429 if (status.status != "pending") {
430 return absl::FailedPreconditionError("Proposal already decided");
431 }
432
433 // Record vote
434 status.votes[username] = approved;
435
436 // Check if decision can be made
437 if (CheckApprovalThreshold(status)) {
438 status.status = "approved";
439 status.decided_at = GetCurrentTimestamp();
440 } else {
441 // Check if rejection threshold reached
442 size_t rejection_count = 0;
443 for (const auto& [user, vote] : status.votes) {
444 if (!vote)
445 rejection_count++;
446 }
447
448 // If host rejected (in host-only mode), reject immediately
449 if (mode_ == ApprovalMode::kHostOnly && username == host_username_ &&
450 !approved) {
451 status.status = "rejected";
452 status.decided_at = GetCurrentTimestamp();
453 }
454 }
455
456 return absl::OkStatus();
457}
458
460 const ApprovalStatus& status) const {
461 switch (mode_) {
463 // Only host vote matters
464 if (status.votes.find(host_username_) != status.votes.end()) {
465 return status.votes.at(host_username_);
466 }
467 return false;
468
470 size_t approval_count = 0;
471 for (const auto& [user, approved] : status.votes) {
472 if (approved)
473 approval_count++;
474 }
475 return approval_count > participants_.size() / 2;
476 }
477
479 if (status.votes.size() < participants_.size()) {
480 return false; // Not everyone voted yet
481 }
482 for (const auto& [user, approved] : status.votes) {
483 if (!approved)
484 return false;
485 }
486 return true;
487 }
488
490 return true;
491 }
492
493 return false;
494}
495
497 const std::string& proposal_id) const {
498 auto it = proposals_.find(proposal_id);
499 if (it == proposals_.end()) {
500 return false;
501 }
502 return it->second.status == "approved";
503}
504
505std::vector<ProposalApprovalManager::ApprovalStatus>
507 std::vector<ApprovalStatus> pending;
508 for (const auto& [id, status] : proposals_) {
509 if (status.status == "pending") {
510 pending.push_back(status);
511 }
512 }
513 return pending;
514}
515
516absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
518 const std::string& proposal_id) const {
519 auto it = proposals_.find(proposal_id);
520 if (it == proposals_.end()) {
521 return absl::NotFoundError("Proposal not found");
522 }
523 return it->second;
524}
525
526} // namespace net
527
528} // 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:28
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:548
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
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