mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-26 17:46:08 -05:00
Merge branch 'bugfix/save-data-recovery' into rewrite/master
This commit is contained in:
commit
56c72716fc
5 changed files with 112 additions and 6 deletions
|
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Senpai (increased the note speed)
|
||||
- Thorns (increased the note speed slightly)
|
||||
- Favorite songs marked in Freeplay are now stored between sessions.
|
||||
- In the event that the game cannot load your save data, it will now perform a backup before clearing it, so that we can try to repair it in the future.
|
||||
- Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!)
|
||||
- Improved logic for NoteHitScriptEvents, allowing you to view the hit diff and modify whether a note hit is a combo break (thanks nebulazorua!)
|
||||
- Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!)
|
||||
|
|
|
@ -16,7 +16,7 @@ import thx.semver.Version;
|
|||
@:nullSafety
|
||||
class Save
|
||||
{
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.4";
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.5";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||
|
||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||
|
@ -56,6 +56,9 @@ class Save
|
|||
if (data == null) this.data = Save.getDefault();
|
||||
else
|
||||
this.data = data;
|
||||
|
||||
// Make sure the verison number is up to date before we flush.
|
||||
this.data.version = Save.SAVE_DATA_VERSION;
|
||||
}
|
||||
|
||||
public static function getDefault():RawSaveData
|
||||
|
@ -713,7 +716,6 @@ class Save
|
|||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||
@:privateAccess
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
else
|
||||
|
@ -725,13 +727,94 @@ class Save
|
|||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Loaded save data.');
|
||||
@:privateAccess
|
||||
trace('[SAVE] Found existing save data.');
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function archiveBadSaveData(data:Dynamic):Int
|
||||
{
|
||||
// We want to save this somewhere so we can try to recover it for the user in the future!
|
||||
|
||||
final RECOVERY_SLOT_START = 1000;
|
||||
|
||||
return writeToAvailableSlot(RECOVERY_SLOT_START, data);
|
||||
}
|
||||
|
||||
public static function debug_queryBadSaveData():Void
|
||||
{
|
||||
final RECOVERY_SLOT_START = 1000;
|
||||
final RECOVERY_SLOT_END = 1100;
|
||||
var firstBadSaveData = querySlotRange(RECOVERY_SLOT_START, RECOVERY_SLOT_END);
|
||||
if (firstBadSaveData > 0)
|
||||
{
|
||||
trace('[SAVE] Found bad save data in slot ${firstBadSaveData}!');
|
||||
trace('We should look into recovery...');
|
||||
|
||||
trace(haxe.Json.stringify(fetchFromSlotRaw(firstBadSaveData)));
|
||||
}
|
||||
}
|
||||
|
||||
static function fetchFromSlotRaw(slot:Int):Null<Dynamic>
|
||||
{
|
||||
var targetSaveData = new FlxSave();
|
||||
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
if (targetSaveData.isEmpty()) return null;
|
||||
return targetSaveData.data;
|
||||
}
|
||||
|
||||
static function writeToAvailableSlot(slot:Int, data:Dynamic):Int
|
||||
{
|
||||
trace('[SAVE] Finding slot to write data to (starting with ${slot})...');
|
||||
|
||||
var targetSaveData = new FlxSave();
|
||||
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
while (!targetSaveData.isEmpty())
|
||||
{
|
||||
// Keep trying to bind to slots until we find an empty slot.
|
||||
trace('[SAVE] Slot ${slot} is taken, continuing...');
|
||||
slot++;
|
||||
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
}
|
||||
|
||||
trace('[SAVE] Writing data to slot ${slot}...');
|
||||
targetSaveData.mergeData(data, true);
|
||||
|
||||
trace('[SAVE] Data written to slot ${slot}!');
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given save slot is not empty.
|
||||
* @param slot The slot number to check.
|
||||
* @return Whether the slot is not empty.
|
||||
*/
|
||||
static function querySlot(slot:Int):Bool
|
||||
{
|
||||
var targetSaveData = new FlxSave();
|
||||
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
return !targetSaveData.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if any of the slots in the given range is not empty.
|
||||
* @param start The starting slot number to check.
|
||||
* @param end The ending slot number to check.
|
||||
* @return The first slot in the range that is not empty, or `-1` if none are.
|
||||
*/
|
||||
static function querySlotRange(start:Int, end:Int):Int
|
||||
{
|
||||
for (i in start...end)
|
||||
{
|
||||
if (querySlot(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static function fetchLegacySaveData():Null<RawSaveData_v1_0_0>
|
||||
{
|
||||
trace("[SAVE] Checking for legacy save data...");
|
||||
|
|
|
@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.0.5] - 2024-05-21
|
||||
### Fixed
|
||||
- Resolved an issue where HTML5 wouldn't store the semantic version properly, causing the game to fail to load the save.
|
||||
|
||||
## [2.0.4] - 2024-05-21
|
||||
### Added
|
||||
- `favoriteSongs:Array<String>` to `Save`
|
||||
|
|
|
@ -35,8 +35,9 @@ class SaveDataMigrator
|
|||
else
|
||||
{
|
||||
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
|
||||
lime.app.Application.current.window.alert(message, "Save Data Failure");
|
||||
trace('[SAVE] ' + message);
|
||||
var slot:Int = Save.archiveBadSaveData(inputData);
|
||||
var fullMessage:String = 'An error occurred migrating your save data.\n${message}\nInvalid data has been moved to save slot ${slot}.';
|
||||
lime.app.Application.current.window.alert(fullMessage, "Save Data Failure");
|
||||
return new Save(Save.getDefault());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class VersionUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
var versionRaw:thx.semver.Version.SemVer = version;
|
||||
trace('${versionRaw} satisfies (${versionRule})? ${version.satisfies(versionRule)}');
|
||||
return version.satisfies(versionRule);
|
||||
}
|
||||
catch (e)
|
||||
|
@ -39,13 +41,28 @@ class VersionUtil
|
|||
if (thx.Types.isAnonymousObject(versionData.version))
|
||||
{
|
||||
// This is bad! versionData.version should be an array!
|
||||
trace('[SAVE] Version data repair required! (got ${versionData.version})');
|
||||
// Turn the objects back into arrays.
|
||||
// I'd use DynamicsT.values but IDK if it maintains order
|
||||
versionData.version = [versionData.version[0], versionData.version[1], versionData.version[2]];
|
||||
|
||||
// This is so jank but it should work.
|
||||
var buildData:Dynamic<String> = cast versionData.build;
|
||||
var buildDataFixed:Array<thx.semver.Version.Identifier> = thx.Dynamics.DynamicsT.values(buildData)
|
||||
.map(function(d:Dynamic) return StringId(d.toString()));
|
||||
versionData.build = buildDataFixed;
|
||||
|
||||
var preData:Dynamic<String> = cast versionData.pre;
|
||||
var preDataFixed:Array<thx.semver.Version.Identifier> = thx.Dynamics.DynamicsT.values(preData).map(function(d:Dynamic) return StringId(d.toString()));
|
||||
versionData.pre = preDataFixed;
|
||||
|
||||
var fixedVersion:thx.semver.Version = versionData;
|
||||
trace('[SAVE] Fixed version: ${fixedVersion}');
|
||||
return fixedVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Version data repair not required (got ${version})');
|
||||
// No need for repair.
|
||||
return version;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue