lint fixes
Some checks failed
Continuous Integration / Code Formatting (pull_request) Successful in 27s
Continuous Integration / Code Quality Check (pull_request) Successful in 29s
Continuous Integration / Test Execution (pull_request) Failing after 33s
Continuous Integration / CI Summary (pull_request) Failing after 5s
Some checks failed
Continuous Integration / Code Formatting (pull_request) Successful in 27s
Continuous Integration / Code Quality Check (pull_request) Successful in 29s
Continuous Integration / Test Execution (pull_request) Failing after 33s
Continuous Integration / CI Summary (pull_request) Failing after 5s
This commit is contained in:
@@ -14,10 +14,6 @@ const MAX_SCORE: int = 999999999
|
||||
const MAX_GAMES_PLAYED: int = 100000
|
||||
const MAX_FILE_SIZE: int = 1048576 # 1MB limit
|
||||
|
||||
# Save operation protection - prevents race conditions
|
||||
var _save_in_progress: bool = false
|
||||
var _restore_in_progress: bool = false
|
||||
|
||||
var game_data: Dictionary = {
|
||||
"high_score": 0,
|
||||
"current_score": 0,
|
||||
@@ -32,6 +28,10 @@ var game_data: Dictionary = {
|
||||
}
|
||||
}
|
||||
|
||||
# Save operation protection - prevents race conditions
|
||||
var _save_in_progress: bool = false
|
||||
var _restore_in_progress: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
"""Initialize SaveManager and load existing save data on startup"""
|
||||
@@ -98,12 +98,22 @@ func load_game() -> void:
|
||||
# Reset restore flag
|
||||
_restore_in_progress = false
|
||||
|
||||
var loaded_data = _load_and_parse_save_file()
|
||||
if loaded_data == null:
|
||||
return
|
||||
|
||||
# Process the loaded data
|
||||
_process_loaded_data(loaded_data)
|
||||
|
||||
|
||||
func _load_and_parse_save_file() -> Variant:
|
||||
"""Load and parse the save file, returning null on failure"""
|
||||
var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
|
||||
if save_file == null:
|
||||
DebugManager.log_error(
|
||||
"Failed to open save file for reading: %s" % SAVE_FILE_PATH, "SaveManager"
|
||||
)
|
||||
return
|
||||
return null
|
||||
|
||||
# Check file size
|
||||
var file_size: int = save_file.get_length()
|
||||
@@ -112,14 +122,14 @@ func load_game() -> void:
|
||||
"Save file too large: %d bytes (max %d)" % [file_size, MAX_FILE_SIZE], "SaveManager"
|
||||
)
|
||||
save_file.close()
|
||||
return
|
||||
return null
|
||||
|
||||
var json_string: Variant = save_file.get_var()
|
||||
save_file.close()
|
||||
|
||||
if not json_string is String:
|
||||
DebugManager.log_error("Save file contains invalid data type", "SaveManager")
|
||||
return
|
||||
return null
|
||||
|
||||
var json: JSON = JSON.new()
|
||||
var parse_result: Error = json.parse(json_string)
|
||||
@@ -127,48 +137,33 @@ func load_game() -> void:
|
||||
DebugManager.log_error(
|
||||
"Failed to parse save file JSON: %s" % json.error_string, "SaveManager"
|
||||
)
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"JSON parse failed and backup restore failed, using defaults", "SaveManager"
|
||||
)
|
||||
return
|
||||
_handle_load_failure("JSON parse failed")
|
||||
return null
|
||||
|
||||
var loaded_data: Variant = json.data
|
||||
if not loaded_data is Dictionary:
|
||||
DebugManager.log_error("Save file root is not a dictionary", "SaveManager")
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"Invalid data format and backup restore failed, using defaults", "SaveManager"
|
||||
)
|
||||
return
|
||||
_handle_load_failure("Invalid data format")
|
||||
return null
|
||||
|
||||
return loaded_data
|
||||
|
||||
|
||||
func _process_loaded_data(loaded_data: Variant) -> void:
|
||||
"""Process and validate the loaded data"""
|
||||
# Validate checksum first
|
||||
if not _validate_checksum(loaded_data):
|
||||
DebugManager.log_error(
|
||||
"Save file checksum validation failed - possible tampering", "SaveManager"
|
||||
)
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"Backup restore failed, using default game data", "SaveManager"
|
||||
)
|
||||
_handle_load_failure("Checksum validation failed")
|
||||
return
|
||||
|
||||
# Handle version migration
|
||||
var migrated_data: Variant = _handle_version_migration(loaded_data)
|
||||
if migrated_data == null:
|
||||
DebugManager.log_error("Save file version migration failed", "SaveManager")
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"Migration failed and backup restore failed, using defaults", "SaveManager"
|
||||
)
|
||||
_handle_load_failure("Migration failed")
|
||||
return
|
||||
|
||||
# Validate and fix loaded data
|
||||
@@ -176,19 +171,21 @@ func load_game() -> void:
|
||||
DebugManager.log_error(
|
||||
"Save file failed validation after migration, using defaults", "SaveManager"
|
||||
)
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"Validation failed and backup restore failed, using defaults", "SaveManager"
|
||||
)
|
||||
_handle_load_failure("Validation failed")
|
||||
return
|
||||
|
||||
# Use migrated data
|
||||
loaded_data = migrated_data
|
||||
|
||||
# Safely merge validated data
|
||||
_merge_validated_data(loaded_data)
|
||||
_merge_validated_data(migrated_data)
|
||||
|
||||
|
||||
func _handle_load_failure(reason: String) -> void:
|
||||
"""Handle load failure with backup restoration attempt"""
|
||||
if not _restore_in_progress:
|
||||
var backup_restored = _restore_backup_if_exists()
|
||||
if not backup_restored:
|
||||
DebugManager.log_warn(
|
||||
"%s and backup restore failed, using defaults" % reason, "SaveManager"
|
||||
)
|
||||
|
||||
DebugManager.log_info(
|
||||
(
|
||||
@@ -375,6 +372,28 @@ func reset_all_progress() -> bool:
|
||||
# Security and validation helper functions
|
||||
func _validate_save_data(data: Dictionary) -> bool:
|
||||
# Check required fields exist and have correct types
|
||||
if not _validate_required_fields(data):
|
||||
return false
|
||||
|
||||
# Validate numeric fields
|
||||
if not _validate_score_fields(data):
|
||||
return false
|
||||
|
||||
# Validate games_played field
|
||||
if not _validate_games_played_field(data):
|
||||
return false
|
||||
|
||||
# Validate grid state
|
||||
var grid_state: Variant = data.get("grid_state", {})
|
||||
if not grid_state is Dictionary:
|
||||
DebugManager.log_error("Grid state is not a dictionary", "SaveManager")
|
||||
return false
|
||||
|
||||
return _validate_grid_state(grid_state)
|
||||
|
||||
|
||||
func _validate_required_fields(data: Dictionary) -> bool:
|
||||
"""Validate that all required fields exist"""
|
||||
var required_fields: Array[String] = [
|
||||
"high_score", "current_score", "games_played", "total_score", "grid_state"
|
||||
]
|
||||
@@ -382,19 +401,21 @@ func _validate_save_data(data: Dictionary) -> bool:
|
||||
if not data.has(field):
|
||||
DebugManager.log_error("Missing required field: %s" % field, "SaveManager")
|
||||
return false
|
||||
return true
|
||||
|
||||
# Validate numeric fields
|
||||
if not _is_valid_score(data.get("high_score", 0)):
|
||||
DebugManager.log_error("Invalid high_score validation failed", "SaveManager")
|
||||
return false
|
||||
if not _is_valid_score(data.get("current_score", 0)):
|
||||
DebugManager.log_error("Invalid current_score validation failed", "SaveManager")
|
||||
return false
|
||||
if not _is_valid_score(data.get("total_score", 0)):
|
||||
DebugManager.log_error("Invalid total_score validation failed", "SaveManager")
|
||||
return false
|
||||
|
||||
# Use safe getter for games_played validation
|
||||
func _validate_score_fields(data: Dictionary) -> bool:
|
||||
"""Validate all score-related fields"""
|
||||
var score_fields = ["high_score", "current_score", "total_score"]
|
||||
for field in score_fields:
|
||||
if not _is_valid_score(data.get(field, 0)):
|
||||
DebugManager.log_error("Invalid %s validation failed" % field, "SaveManager")
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _validate_games_played_field(data: Dictionary) -> bool:
|
||||
"""Validate the games_played field"""
|
||||
var games_played: Variant = data.get("games_played", 0)
|
||||
if not (games_played is int or games_played is float):
|
||||
DebugManager.log_error(
|
||||
@@ -418,13 +439,7 @@ func _validate_save_data(data: Dictionary) -> bool:
|
||||
)
|
||||
return false
|
||||
|
||||
# Validate grid state
|
||||
var grid_state: Variant = data.get("grid_state", {})
|
||||
if not grid_state is Dictionary:
|
||||
DebugManager.log_error("Grid state is not a dictionary", "SaveManager")
|
||||
return false
|
||||
|
||||
return _validate_grid_state(grid_state)
|
||||
return true
|
||||
|
||||
|
||||
func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
@@ -522,30 +537,71 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool:
|
||||
|
||||
|
||||
func _validate_grid_state(grid_state: Dictionary) -> bool:
|
||||
# Check grid size
|
||||
# Validate grid size
|
||||
var grid_size_validation = _validate_grid_size(grid_state)
|
||||
if not grid_size_validation.valid:
|
||||
return false
|
||||
var width = grid_size_validation.width
|
||||
var height = grid_size_validation.height
|
||||
|
||||
# Validate tile types
|
||||
var tile_types = _validate_tile_types(grid_state)
|
||||
if tile_types == -1:
|
||||
return false
|
||||
|
||||
# Validate active gem types
|
||||
if not _validate_active_gem_types(grid_state, tile_types):
|
||||
return false
|
||||
|
||||
# Validate grid layout if present
|
||||
var layout: Variant = grid_state.get("grid_layout", [])
|
||||
if not layout is Array:
|
||||
DebugManager.log_error("grid_layout is not an array", "SaveManager")
|
||||
return false
|
||||
|
||||
if layout.size() > 0:
|
||||
return _validate_grid_layout(layout, width, height, tile_types)
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func _validate_grid_size(grid_state: Dictionary) -> Dictionary:
|
||||
"""Validate grid size and return validation result with dimensions"""
|
||||
var result = {"valid": false, "width": 0, "height": 0}
|
||||
|
||||
if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
|
||||
DebugManager.log_error("Invalid grid_size in save data", "SaveManager")
|
||||
return false
|
||||
return result
|
||||
|
||||
var size: Variant = grid_state.grid_size
|
||||
if not size.has("x") or not size.has("y"):
|
||||
return false
|
||||
return result
|
||||
|
||||
var width: Variant = size.x
|
||||
var height: Variant = size.y
|
||||
if not width is int or not height is int:
|
||||
return false
|
||||
return result
|
||||
if width < 3 or height < 3 or width > MAX_GRID_SIZE or height > MAX_GRID_SIZE:
|
||||
DebugManager.log_error("Grid size out of bounds: %dx%d" % [width, height], "SaveManager")
|
||||
return false
|
||||
return result
|
||||
|
||||
# Check tile types
|
||||
result.valid = true
|
||||
result.width = width
|
||||
result.height = height
|
||||
return result
|
||||
|
||||
|
||||
func _validate_tile_types(grid_state: Dictionary) -> int:
|
||||
"""Validate tile types count and return it, or -1 if invalid"""
|
||||
var tile_types: Variant = grid_state.get("tile_types_count", 0)
|
||||
if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
||||
DebugManager.log_error("Invalid tile_types_count: %s" % str(tile_types), "SaveManager")
|
||||
return false
|
||||
return -1
|
||||
return tile_types
|
||||
|
||||
# Validate active_gem_types if present
|
||||
|
||||
func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool:
|
||||
"""Validate active gem types array"""
|
||||
var active_gems: Variant = grid_state.get("active_gem_types", [])
|
||||
if not active_gems is Array:
|
||||
DebugManager.log_error("active_gem_types is not an array", "SaveManager")
|
||||
@@ -565,16 +621,6 @@ func _validate_grid_state(grid_state: Dictionary) -> bool:
|
||||
"active_gem_types[%d] out of range: %d" % [i, gem_type], "SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
# Validate grid layout if present
|
||||
var layout: Variant = grid_state.get("grid_layout", [])
|
||||
if not layout is Array:
|
||||
DebugManager.log_error("grid_layout is not an array", "SaveManager")
|
||||
return false
|
||||
|
||||
if layout.size() > 0:
|
||||
return _validate_grid_layout(layout, width, height, tile_types)
|
||||
|
||||
return true
|
||||
|
||||
|
||||
@@ -757,22 +803,30 @@ func _normalize_value_for_checksum(value: Variant) -> String:
|
||||
"""
|
||||
if value == null:
|
||||
return "null"
|
||||
elif value is bool:
|
||||
|
||||
if value is bool:
|
||||
return str(value)
|
||||
elif value is int:
|
||||
|
||||
if value is String:
|
||||
return value
|
||||
|
||||
if value is int:
|
||||
# Convert to int string format to match JSON deserialized floats
|
||||
return str(int(value))
|
||||
elif value is float:
|
||||
# Convert float to int if it's a whole number (handles JSON conversion)
|
||||
if value == int(value):
|
||||
return str(int(value))
|
||||
else:
|
||||
# For actual floats, use consistent precision
|
||||
return "%.10f" % value
|
||||
elif value is String:
|
||||
return value
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
if value is float:
|
||||
return _normalize_float_for_checksum(value)
|
||||
|
||||
return str(value)
|
||||
|
||||
|
||||
func _normalize_float_for_checksum(value: float) -> String:
|
||||
"""Normalize float values for checksum calculation"""
|
||||
# Convert float to int if it's a whole number (handles JSON conversion)
|
||||
if value == int(value):
|
||||
return str(int(value))
|
||||
# For actual floats, use consistent precision
|
||||
return "%.10f" % value
|
||||
|
||||
|
||||
func _validate_checksum(data: Dictionary) -> bool:
|
||||
@@ -790,15 +844,15 @@ func _validate_checksum(data: Dictionary) -> bool:
|
||||
# Try to be more lenient with existing saves to prevent data loss
|
||||
var data_version: Variant = data.get("_version", 0)
|
||||
if data_version <= 1:
|
||||
(
|
||||
DebugManager
|
||||
. log_warn(
|
||||
(
|
||||
"Checksum mismatch in v%d save file - may be due to JSON serialization issue (stored: %s, calculated: %s)"
|
||||
DebugManager.log_warn(
|
||||
(
|
||||
"Checksum mismatch in v%d save file - may be due to JSON serialization issue "
|
||||
+ (
|
||||
"(stored: %s, calculated: %s)"
|
||||
% [data_version, stored_checksum, calculated_checksum]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
)
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
(
|
||||
DebugManager
|
||||
@@ -810,15 +864,14 @@ func _validate_checksum(data: Dictionary) -> bool:
|
||||
# Mark for checksum regeneration by removing the invalid one
|
||||
data.erase("_checksum")
|
||||
return true
|
||||
else:
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"Checksum mismatch - stored: %s, calculated: %s"
|
||||
% [stored_checksum, calculated_checksum]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"Checksum mismatch - stored: %s, calculated: %s"
|
||||
% [stored_checksum, calculated_checksum]
|
||||
),
|
||||
"SaveManager"
|
||||
)
|
||||
return false
|
||||
|
||||
return is_valid
|
||||
|
||||
@@ -880,7 +933,7 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
||||
"Save file is current version (%d)" % SAVE_FORMAT_VERSION, "SaveManager"
|
||||
)
|
||||
return data
|
||||
elif data_version > SAVE_FORMAT_VERSION:
|
||||
if data_version > SAVE_FORMAT_VERSION:
|
||||
# Future version - cannot handle
|
||||
DebugManager.log_error(
|
||||
(
|
||||
@@ -890,13 +943,12 @@ func _handle_version_migration(data: Dictionary) -> Variant:
|
||||
"SaveManager"
|
||||
)
|
||||
return null
|
||||
else:
|
||||
# Older version - migrate
|
||||
DebugManager.log_info(
|
||||
"Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION],
|
||||
"SaveManager"
|
||||
)
|
||||
return _migrate_save_data(data, data_version)
|
||||
# Older version - migrate
|
||||
DebugManager.log_info(
|
||||
"Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION],
|
||||
"SaveManager"
|
||||
)
|
||||
return _migrate_save_data(data, data_version)
|
||||
|
||||
|
||||
func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
|
||||
|
||||
Reference in New Issue
Block a user