1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-01-12 23:27:35 +00:00

Merge pull request #460 from FunkinCrew/save-merging-fix

Fix issue with deepMerge() that corrupted saves.
This commit is contained in:
Cameron Taylor 2024-04-04 03:39:58 -04:00 committed by GitHub
commit a4f8cb7a59
5 changed files with 151 additions and 1 deletions

View file

@ -139,7 +139,7 @@
"name": "openfl",
"type": "git",
"dir": null,
"ref": "f229d76361c7e31025a048fe7909847f75bb5d5e",
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
"url": "https://github.com/FunkinCrew/openfl"
},
{

View file

@ -9,6 +9,7 @@ import funkin.save.migrator.SaveDataMigrator;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
import thx.semver.Version;
import funkin.util.SerializerUtil;
@:nullSafety
class Save
@ -391,6 +392,22 @@ class Save
*/
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
{
if (data.scores?.levels == null)
{
if (data.scores == null)
{
data.scores =
{
songs: [],
levels: []
};
}
else
{
data.scores.levels = [];
}
}
var level = data.scores.levels.get(levelId);
if (level == null)
{
@ -641,6 +658,9 @@ class Save
{
trace("[SAVE] Loading save from slot " + slot + "...");
// Prevent crashes if the save data is corrupted.
SerializerUtil.initSerializer();
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
if (FlxG.save.isEmpty())

View file

@ -63,6 +63,31 @@ class SerializerUtil
}
}
public static function initSerializer():Void
{
haxe.Unserializer.DEFAULT_RESOLVER = new FunkinTypeResolver();
}
/**
* Serialize a Haxe object using the built-in Serializer.
* @param input The object to serialize
* @return The serialized object as a string
*/
public static function fromHaxeObject(input:Dynamic):String
{
return haxe.Serializer.run(input);
}
/**
* Convert a serialized Haxe object back into a Haxe object.
* @param input The serialized object as a string
* @return The deserialized object
*/
public static function toHaxeObject(input:String):Dynamic
{
return haxe.Unserializer.run(input);
}
/**
* Customize how certain types are serialized when converting to JSON.
*/
@ -90,3 +115,26 @@ class SerializerUtil
return result;
}
}
class FunkinTypeResolver
{
public function new()
{
// Blank constructor.
}
public function resolveClass(name:String):Class<Dynamic>
{
if (name == 'Dynamic')
{
FlxG.log.warn('Found invalid class type in save data, indicates partial save corruption.');
return null;
}
return Type.resolveClass(name);
};
public function resolveEnum(name:String):Enum<Dynamic>
{
return Type.resolveEnum(name);
};
}

View file

@ -1,5 +1,6 @@
package funkin.util;
import funkin.util.tools.MapTools;
import haxe.DynamicAccess;
/**
@ -26,6 +27,57 @@ class StructureUtil
return result;
}
public static function toMap(a:Dynamic):haxe.ds.Map<String, Dynamic>
{
var result:haxe.ds.Map<String, Dynamic> = [];
for (field in Reflect.fields(a))
{
result.set(field, Reflect.field(a, field));
}
return result;
}
public static function isMap(a:Dynamic):Bool
{
return Std.isOfType(a, haxe.Constraints.IMap);
}
public static function isObject(a:Dynamic):Bool
{
switch (Type.typeof(a))
{
case TObject:
return true;
default:
return false;
}
}
public static function isPrimitive(a:Dynamic):Bool
{
switch (Type.typeof(a))
{
case TInt | TFloat | TBool:
return true;
case TClass(c):
return false;
case TEnum(e):
return false;
case TObject:
return false;
case TFunction:
return false;
case TNull:
return true;
case TUnknown:
return false;
default:
return false;
}
}
/**
* Merge two structures, with the second overwriting the first.
* Performs a DEEP clone, where child structures are also merged recursively.
@ -37,6 +89,18 @@ class StructureUtil
{
if (a == null) return b;
if (b == null) return null;
if (isPrimitive(a) && isPrimitive(b)) return b;
if (isMap(b))
{
if (isMap(a))
{
return MapTools.merge(a, b);
}
else
{
return StructureUtil.toMap(a).merge(b);
}
}
if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b;
var result:DynamicAccess<Dynamic> = Reflect.copy(a);

View file

@ -33,6 +33,24 @@ class MapTools
return map.copy();
}
/**
* Create a new map which is a combination of the two given maps.
* @param a The base map.
* @param b The other map. The values from this take precedence.
* @return The combined map.
*/
public static function merge<K, T>(a:Map<K, T>, b:Map<K, T>):Map<K, T>
{
var result = a.copy();
for (pair in b.keyValueIterator())
{
result.set(pair.key, pair.value);
}
return result;
}
/**
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
*/