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