-
Notifications
You must be signed in to change notification settings - Fork 0
changed saving system to sqlite #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,378 @@ | ||||||||||||||||
| # Dragon Riders Save System Documentation | ||||||||||||||||
|
|
||||||||||||||||
| ## Overview | ||||||||||||||||
|
|
||||||||||||||||
| Dragon Riders uses a secure SQLite-based save system to store game data. This system is designed to: | ||||||||||||||||
|
|
||||||||||||||||
| - **Scale efficiently** - Handle hundreds or thousands of dragons without performance degradation | ||||||||||||||||
| - **Prevent cheating** - Encrypt sensitive data and verify integrity with HMAC hashes | ||||||||||||||||
| - **Provide safety** - Automatic migration from legacy JSON saves with backup support | ||||||||||||||||
| - **Optimize storage** - Compress data and use efficient indexing | ||||||||||||||||
|
|
||||||||||||||||
| ## Architecture | ||||||||||||||||
|
|
||||||||||||||||
| ### Database Structure | ||||||||||||||||
|
|
||||||||||||||||
| The save system uses SQLite with the following tables: | ||||||||||||||||
|
|
||||||||||||||||
| #### `dragons` Table | ||||||||||||||||
| Stores individual dragon data with encryption: | ||||||||||||||||
| - `id` - Unique dragon identifier (auto-increment) | ||||||||||||||||
| - `dragon_type` - Type of dragon (e.g., "fire", "ice") - indexed for fast queries | ||||||||||||||||
| - `name` - Dragon's name - stored for quick access | ||||||||||||||||
| - `stage` - Current growth stage - indexed for filtering | ||||||||||||||||
| - `encrypted_data` - Encrypted blob containing all dragon stats, progress, and enchantments | ||||||||||||||||
| - `data_hash` - HMAC-SHA256 hash for integrity verification | ||||||||||||||||
| - `created_at` - Timestamp of dragon creation | ||||||||||||||||
| - `updated_at` - Timestamp of last update | ||||||||||||||||
|
|
||||||||||||||||
| #### `game_state` Table | ||||||||||||||||
| Stores global game state (coins, meat, settings): | ||||||||||||||||
| - `id` - Always 1 (single row table) | ||||||||||||||||
| - `encrypted_data` - Encrypted game state data | ||||||||||||||||
| - `data_hash` - HMAC-SHA256 hash for integrity verification | ||||||||||||||||
| - `updated_at` - Timestamp of last update | ||||||||||||||||
|
|
||||||||||||||||
| #### `eggs` Table | ||||||||||||||||
| Stores eggs waiting to hatch: | ||||||||||||||||
| - `id` - Unique egg identifier | ||||||||||||||||
| - `dragon_type` - Type of dragon that will hatch | ||||||||||||||||
| - `clicks` - Number of clicks toward hatching | ||||||||||||||||
| - `created_at` - Timestamp of egg creation | ||||||||||||||||
|
|
||||||||||||||||
| #### `inventory` Table | ||||||||||||||||
| Stores player's item inventory: | ||||||||||||||||
| - `item_id` - Unique item identifier (primary key) | ||||||||||||||||
| - `quantity` - Number of items owned | ||||||||||||||||
| - `updated_at` - Timestamp of last update | ||||||||||||||||
|
|
||||||||||||||||
| #### `metadata` Table | ||||||||||||||||
| Stores system metadata: | ||||||||||||||||
| - `key` - Metadata key (e.g., "db_version") | ||||||||||||||||
| - `value` - Metadata value | ||||||||||||||||
|
|
||||||||||||||||
| ### Security Features | ||||||||||||||||
|
|
||||||||||||||||
| #### 1. Encryption | ||||||||||||||||
| All sensitive data (dragon stats, coins, meat, inventory) is encrypted using: | ||||||||||||||||
| - **Algorithm**: Fernet (symmetric encryption using AES-128-CBC) | ||||||||||||||||
| - **Key Derivation**: SHA-256 hash of secret key | ||||||||||||||||
| - **Compression**: zlib compression before encryption to reduce size | ||||||||||||||||
|
|
||||||||||||||||
| #### 2. Integrity Verification | ||||||||||||||||
| Every encrypted data blob has an accompanying HMAC hash: | ||||||||||||||||
| - **Algorithm**: HMAC-SHA256 | ||||||||||||||||
| - **Purpose**: Detect any tampering with save data | ||||||||||||||||
| - **Behavior**: Save data is rejected if hash verification fails | ||||||||||||||||
|
|
||||||||||||||||
| #### 3. Anti-Cheat Measures | ||||||||||||||||
| - Encryption prevents simple hex editing of save files | ||||||||||||||||
| - HMAC ensures modifications are detected | ||||||||||||||||
| - Secret keys are obfuscated in source code | ||||||||||||||||
| - Database structure makes mass editing difficult | ||||||||||||||||
|
|
||||||||||||||||
| **Note**: This is client-side security and determined cheaters can still modify data. For competitive features, use server-side validation. | ||||||||||||||||
|
|
||||||||||||||||
| ## Migration from JSON Saves | ||||||||||||||||
|
|
||||||||||||||||
| ### Automatic Migration | ||||||||||||||||
|
|
||||||||||||||||
| The game automatically migrates old JSON saves to SQLite: | ||||||||||||||||
|
|
||||||||||||||||
| 1. On first run, checks for existing `dragon_save.json` | ||||||||||||||||
| 2. If found, loads the JSON data | ||||||||||||||||
| 3. Converts and saves to new `dragon_save.db` SQLite database | ||||||||||||||||
| 4. Preserves original JSON file as backup | ||||||||||||||||
|
|
||||||||||||||||
| ### Manual Migration | ||||||||||||||||
|
|
||||||||||||||||
| You can verify migration success using the utility: | ||||||||||||||||
|
|
||||||||||||||||
| ```bash | ||||||||||||||||
| python -m src.utils.save_migration verify | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### Backup Management | ||||||||||||||||
|
|
||||||||||||||||
| Create a backup before migration: | ||||||||||||||||
| ```bash | ||||||||||||||||
| python -m src.utils.save_migration backup | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| View save file information: | ||||||||||||||||
| ```bash | ||||||||||||||||
| python -m src.utils.save_migration info | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| Clean up old backups (keep 5 most recent): | ||||||||||||||||
| ```bash | ||||||||||||||||
| python -m src.utils.save_migration cleanup | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ## Performance Considerations | ||||||||||||||||
|
|
||||||||||||||||
| ### Indexing | ||||||||||||||||
| The database includes indexes on commonly queried fields: | ||||||||||||||||
| - `dragons.dragon_type` - Fast filtering by type | ||||||||||||||||
| - `dragons.stage` - Fast filtering by growth stage | ||||||||||||||||
| - `dragons.created_at` - Fast chronological sorting | ||||||||||||||||
|
|
||||||||||||||||
| ### Query Optimization | ||||||||||||||||
| - Individual dragons can be loaded without loading entire collection | ||||||||||||||||
| - Bulk operations use transactions for atomicity | ||||||||||||||||
| - Prepared statements prevent SQL injection and improve performance | ||||||||||||||||
|
|
||||||||||||||||
| ### Storage Efficiency | ||||||||||||||||
| - Data is compressed before encryption (typically 50-70% size reduction) | ||||||||||||||||
| - Only changed data is updated (not full rewrites) | ||||||||||||||||
| - `VACUUM` command can reclaim space from deleted dragons | ||||||||||||||||
|
|
||||||||||||||||
| ### Scaling | ||||||||||||||||
| With hundreds of dragons: | ||||||||||||||||
| - **JSON System**: O(n) load time - loads all dragons at once | ||||||||||||||||
| - **SQLite System**: O(1) metadata load + O(k) where k = dragons to display | ||||||||||||||||
| - **Result**: SQLite remains fast even with 1000+ dragons | ||||||||||||||||
|
|
||||||||||||||||
| ## API Reference | ||||||||||||||||
|
|
||||||||||||||||
| ### SQLiteSaveSystem | ||||||||||||||||
|
|
||||||||||||||||
| Main save system class in `src/save_system_sqlite.py`. | ||||||||||||||||
|
|
||||||||||||||||
| #### Constructor | ||||||||||||||||
| ```python | ||||||||||||||||
| save_system = SQLiteSaveSystem(db_file="dragon_save.db") | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Saving Data | ||||||||||||||||
|
|
||||||||||||||||
| **Save complete game state:** | ||||||||||||||||
| ```python | ||||||||||||||||
| game_data = game_state.to_dict() | ||||||||||||||||
| success = save_system.save_all(game_data) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Save individual dragon:** | ||||||||||||||||
| ```python | ||||||||||||||||
| dragon_data = dragon.to_dict() | ||||||||||||||||
| dragon_id = save_system.save_dragon(dragon_data) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Save game state (coins, meat, settings):** | ||||||||||||||||
| ```python | ||||||||||||||||
| state_data = { | ||||||||||||||||
| "coins": 1000, | ||||||||||||||||
| "meat": 50, | ||||||||||||||||
| "volume": 0.5, | ||||||||||||||||
| # ... | ||||||||||||||||
| } | ||||||||||||||||
| success = save_system.save_game_state(state_data) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Loading Data | ||||||||||||||||
|
|
||||||||||||||||
| **Load complete game state:** | ||||||||||||||||
| ```python | ||||||||||||||||
| game_data = save_system.load_all() | ||||||||||||||||
| if game_data: | ||||||||||||||||
| game_state.from_dict(game_data) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Load individual dragon:** | ||||||||||||||||
| ```python | ||||||||||||||||
| dragon_data = save_system.load_dragon(dragon_id) | ||||||||||||||||
| if dragon_data: | ||||||||||||||||
| dragon = Dragon.from_dict(dragon_data) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Load all dragons:** | ||||||||||||||||
| ```python | ||||||||||||||||
| all_dragons = save_system.load_all_dragons() | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Load dragons by stage:** | ||||||||||||||||
| ```python | ||||||||||||||||
| adult_dragons = save_system.get_dragons_by_stage("adult") | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Deleting Data | ||||||||||||||||
|
|
||||||||||||||||
| **Delete individual dragon:** | ||||||||||||||||
| ```python | ||||||||||||||||
| success = save_system.delete_dragon(dragon_id) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Delete all save data:** | ||||||||||||||||
| ```python | ||||||||||||||||
| success = save_system.delete_all() | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Utility Functions | ||||||||||||||||
|
|
||||||||||||||||
| **Check if save exists:** | ||||||||||||||||
| ```python | ||||||||||||||||
| has_save = save_system.exists() | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Get dragon count:** | ||||||||||||||||
| ```python | ||||||||||||||||
| count = save_system.get_dragon_count() | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| **Optimize database:** | ||||||||||||||||
| ```python | ||||||||||||||||
| save_system.vacuum() # Reclaim unused space | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### GameState Integration | ||||||||||||||||
|
|
||||||||||||||||
| The `GameState` class automatically uses the SQLite system. | ||||||||||||||||
|
|
||||||||||||||||
| #### Saving | ||||||||||||||||
| ```python | ||||||||||||||||
| game_state.save() # Automatically uses SQLite | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Loading | ||||||||||||||||
| ```python | ||||||||||||||||
| success = game_state.load() # Tries SQLite first, falls back to JSON | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| #### Database Optimization | ||||||||||||||||
| ```python | ||||||||||||||||
| game_state.vacuum_database() # Call periodically (e.g., on game exit) | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ## Best Practices | ||||||||||||||||
|
|
||||||||||||||||
| ### When to Save | ||||||||||||||||
| - **Auto-save**: Every 30-60 seconds during gameplay | ||||||||||||||||
| - **Manual save**: When player explicitly saves | ||||||||||||||||
| - **Exit save**: Always save when game closes | ||||||||||||||||
| - **After important actions**: Dragon hatching, purchases, etc. | ||||||||||||||||
|
|
||||||||||||||||
| ### When to Vacuum | ||||||||||||||||
| - On game exit (if many dragons were deleted) | ||||||||||||||||
| - Periodically in background (e.g., every 100 dragon deletions) | ||||||||||||||||
| - After bulk delete operations | ||||||||||||||||
|
|
||||||||||||||||
| ### Error Handling | ||||||||||||||||
| Always check return values: | ||||||||||||||||
| ```python | ||||||||||||||||
| if not game_state.save(): | ||||||||||||||||
| # Notify player that save failed | ||||||||||||||||
| show_error_message("Failed to save game!") | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### Backup Strategy | ||||||||||||||||
| - Automatic backups before migration | ||||||||||||||||
| - Optional periodic backups (weekly/monthly) | ||||||||||||||||
| - Keep 5-10 most recent backups | ||||||||||||||||
| - Store backups in `save_backups/` directory | ||||||||||||||||
|
|
||||||||||||||||
| ## Troubleshooting | ||||||||||||||||
|
|
||||||||||||||||
| ### Save File Not Loading | ||||||||||||||||
| 1. Check if file exists: `ls dragon_save.db` | ||||||||||||||||
| 2. Verify file permissions | ||||||||||||||||
| 3. Check for error messages in console | ||||||||||||||||
| 4. Try loading backup if available | ||||||||||||||||
|
|
||||||||||||||||
| ### Integrity Check Failed | ||||||||||||||||
| This means the save file was tampered with: | ||||||||||||||||
| - Restore from backup | ||||||||||||||||
| - If no backup, save data may be lost | ||||||||||||||||
| - This prevents loading corrupted/cheated saves | ||||||||||||||||
|
|
||||||||||||||||
| ### Migration Issues | ||||||||||||||||
| If migration from JSON fails: | ||||||||||||||||
| 1. Check JSON file is valid: `python -m json.tool dragon_save.json` | ||||||||||||||||
| 2. Create manual backup: `cp dragon_save.json dragon_save.backup.json` | ||||||||||||||||
| 3. Try loading just the JSON: Use `SaveSystem` class directly | ||||||||||||||||
|
|
||||||||||||||||
| ### Database Corruption | ||||||||||||||||
| If SQLite database is corrupted: | ||||||||||||||||
| 1. Try SQLite recovery: `sqlite3 dragon_save.db ".recover" > recovered.sql` | ||||||||||||||||
| 2. Restore from backup | ||||||||||||||||
| 3. As last resort, start new game (JSON backup may have older data) | ||||||||||||||||
|
|
||||||||||||||||
| ### Performance Issues | ||||||||||||||||
| If game becomes slow with many dragons: | ||||||||||||||||
| 1. Run vacuum: `game_state.vacuum_database()` | ||||||||||||||||
| 2. Check database size: `ls -lh dragon_save.db` | ||||||||||||||||
| 3. Consider archiving old dragons (export to separate file) | ||||||||||||||||
|
|
||||||||||||||||
| ## File Locations | ||||||||||||||||
|
|
||||||||||||||||
| ### Save Files | ||||||||||||||||
| - **SQLite Database**: `dragon_save.db` | ||||||||||||||||
| - **Legacy JSON Save**: `dragon_save.json` (kept as backup after migration) | ||||||||||||||||
|
|
||||||||||||||||
| ### Backups | ||||||||||||||||
| - **Directory**: `save_backups/` | ||||||||||||||||
| - **Format**: `dragon_save_backup_YYYYMMDD_HHMMSS.db` or `.json` | ||||||||||||||||
|
|
||||||||||||||||
| ### Temporary Files | ||||||||||||||||
| - SQLite may create temporary files: `dragon_save.db-journal`, `dragon_save.db-wal` | ||||||||||||||||
| - These are normal and should not be deleted manually | ||||||||||||||||
|
|
||||||||||||||||
| ## Security Considerations | ||||||||||||||||
|
|
||||||||||||||||
| ### Client-Side Limitations | ||||||||||||||||
| - All encryption/verification happens on client | ||||||||||||||||
| - Determined cheaters can still modify saves | ||||||||||||||||
| - Recommend online leaderboards use server-side validation | ||||||||||||||||
|
|
||||||||||||||||
| ### Improving Security | ||||||||||||||||
| For competitive features, consider: | ||||||||||||||||
| 1. Server-side save storage | ||||||||||||||||
| 2. Periodic server validation | ||||||||||||||||
| 3. Checksums of game binaries | ||||||||||||||||
| 4. Rate limiting for suspicious actions | ||||||||||||||||
|
|
||||||||||||||||
| ### Data Privacy | ||||||||||||||||
| - Save files are stored locally | ||||||||||||||||
| - No data is sent to external servers | ||||||||||||||||
| - Encryption protects against casual viewing | ||||||||||||||||
| - Backups should be managed by user | ||||||||||||||||
|
|
||||||||||||||||
| ## Future Enhancements | ||||||||||||||||
|
|
||||||||||||||||
| Potential improvements to the save system: | ||||||||||||||||
|
|
||||||||||||||||
| 1. **Cloud Sync**: Optional cloud storage for cross-device play | ||||||||||||||||
| 2. **Compression Levels**: Adjustable compression for size vs. speed | ||||||||||||||||
| 3. **Incremental Saves**: Only save changed data | ||||||||||||||||
| 4. **Save Profiles**: Multiple save slots per user | ||||||||||||||||
| 5. **Export/Import**: Share dragons between saves or players | ||||||||||||||||
| 6. **Statistics**: Track save file metrics (dragon count, playtime, etc.) | ||||||||||||||||
|
|
||||||||||||||||
| ## Technical Details | ||||||||||||||||
|
|
||||||||||||||||
| ### Encryption Specifics | ||||||||||||||||
| - **Algorithm**: Fernet (AES-128-CBC + HMAC-SHA256) | ||||||||||||||||
| - **Key Size**: 256 bits (derived from secret) | ||||||||||||||||
| - **IV**: Unique per encryption (handled by Fernet) | ||||||||||||||||
| - **Padding**: PKCS7 padding (handled by Fernet) | ||||||||||||||||
|
|
||||||||||||||||
|
Comment on lines
+353
to
+357
|
||||||||||||||||
| - **Algorithm**: Fernet (AES-128-CBC + HMAC-SHA256) | |
| - **Key Size**: 256 bits (derived from secret) | |
| - **IV**: Unique per encryption (handled by Fernet) | |
| - **Padding**: PKCS7 padding (handled by Fernet) | |
| - **Algorithm**: Fernet encryption (see cryptography library documentation) | |
| - **Key Size**: 256 bits (Fernet key, derived from secret) | |
| - **IV and Padding**: Handled internally by Fernet |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The performance metrics claim specific timings for saves and loads with 1000 dragons, but these are highly dependent on hardware, operating system, and SQLite configuration. Without specifying the test environment (CPU, disk type, OS, SQLite version), these numbers are not reproducible and could mislead users about expected performance. Consider either: (1) adding test environment details, (2) expressing performance as "typical ranges" rather than specific values, or (3) focusing on relative performance (e.g., "Single dragon operations are ~100-200x faster than full saves").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing word "if" in the sentence. Should be "Check if JSON file is valid" for grammatical correctness.