From 4f2f28cb317f38ee506548061a3ef9d211fbdf02 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Apr 2024 15:05:54 -0400 Subject: [PATCH 1/4] Fix issue with deepMerge() caused by handling maps incorrectly, causing an unhandleable crash. --- source/funkin/util/StructureUtil.hx | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/source/funkin/util/StructureUtil.hx b/source/funkin/util/StructureUtil.hx index 351d0e0a8..f94de4652 100644 --- a/source/funkin/util/StructureUtil.hx +++ b/source/funkin/util/StructureUtil.hx @@ -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 + { + var result:haxe.ds.Map = []; + + 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 = Reflect.copy(a); From b39712d33f6d36ddcf14c8fb050572abb9562549 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Apr 2024 20:31:34 -0400 Subject: [PATCH 2/4] Prevent crashes when the game attempts to load bad save data. --- hmm.json | 2 +- source/funkin/save/Save.hx | 4 +++ source/funkin/util/SerializerUtil.hx | 48 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 0dfe88ded..641ef1bbd 100644 --- a/hmm.json +++ b/hmm.json @@ -139,7 +139,7 @@ "name": "openfl", "type": "git", "dir": null, - "ref": "f229d76361c7e31025a048fe7909847f75bb5d5e", + "ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134", "url": "https://github.com/FunkinCrew/openfl" }, { diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index dc7c5f989..6f2146a7a 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -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 @@ -641,6 +642,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()) diff --git a/source/funkin/util/SerializerUtil.hx b/source/funkin/util/SerializerUtil.hx index c87d3f6c0..fa602cc73 100644 --- a/source/funkin/util/SerializerUtil.hx +++ b/source/funkin/util/SerializerUtil.hx @@ -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 + { + 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 + { + return Type.resolveEnum(name); + }; +} From d8903f138f0a3f9c3c8b65f7dcb26630e2453bac Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Apr 2024 20:50:51 -0400 Subject: [PATCH 3/4] Additional save fix --- source/funkin/save/Save.hx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 6f2146a7a..af2730ddd 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -392,6 +392,22 @@ class Save */ public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null { + 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) { From 3ac466aa5ea866573e4e9181630b566e1416e444 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 4 Apr 2024 03:35:36 -0400 Subject: [PATCH 4/4] Add missing MapTools function from #459 --- source/funkin/util/tools/MapTools.hx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/funkin/util/tools/MapTools.hx b/source/funkin/util/tools/MapTools.hx index 1399fb791..b98cb0adf 100644 --- a/source/funkin/util/tools/MapTools.hx +++ b/source/funkin/util/tools/MapTools.hx @@ -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(a:Map, b:Map):Map + { + 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. */