Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Error With Binary Formatter

Discussion in 'Scripting' started by Mashimaro7, Dec 19, 2020.

  1. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    I wrote a serialization class, and a save data class. But for some reason I keep getting an error on save,

    SerializationException: Type 'UnityEngine.MonoBehaviour' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

    I don't understand, my save data class is marked as serializable. Here's my save data class,

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class SaveData : MonoBehaviour
    7. {
    8.     private static SaveData _data;
    9.  
    10.     public static SaveData data
    11.     {
    12.         get
    13.         {
    14.             if(_data == null)
    15.             {
    16.                 _data = new SaveData();
    17.             }
    18.  
    19.             return _data;
    20.         }
    21.     }
    22.  
    23.     public double currentCoins;
    24.     public int[] producerLevels;
    25.     public int[] upgradeLevels;
    26.     public int nuts;
    27.  
    28.     private void Awake()
    29.     {
    30.         Load();
    31.     }
    32.  
    33.     private void OnApplicationQuit()
    34.     {
    35.         Serialization.Save("Save", data);
    36.     }
    37.  
    38.     public void Load()
    39.     {
    40.         _data = (SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save");
    41.     }
    42. }
    43.  
    The variables are being assigned in the scripts, also here's my serialization script,

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Runtime.Serialization.Formatters.Binary;
    5. using UnityEngine;
    6.  
    7. public class Serialization
    8. {
    9.  
    10.  
    11.  
    12.     public static bool Save(string saveName,object saveData)
    13.     {
    14.         BinaryFormatter formatter = GetBinaryFormatter();
    15.  
    16.         if(!Directory.Exists(Application.persistentDataPath + "/saves"))
    17.         {
    18.             Directory.CreateDirectory(Application.persistentDataPath + "/saves");
    19.         }
    20.  
    21.         string path = Application.persistentDataPath + "/saves" + saveName + ".save";
    22.  
    23.         FileStream file = File.Create(path);
    24.         formatter.Serialize(file,saveData);
    25.  
    26.         file.Close();
    27.  
    28.         return false;
    29.     }
    30.  
    31.     public static object Load(string path)
    32.     {
    33.         if (!File.Exists(path))
    34.         {
    35.             return null;
    36.         }
    37.  
    38.         BinaryFormatter formatter = GetBinaryFormatter();
    39.  
    40.         FileStream file = File.Open(path,FileMode.Open);
    41.  
    42.         try
    43.         {
    44.             object save = formatter.Deserialize(file);
    45.             file.Close();
    46.             return save;
    47.         }
    48.         catch
    49.         {
    50.             Debug.LogErrorFormat("Failed to load file at {0}", path);
    51.             file.Close();
    52.             return null;
    53.         }
    54.     }
    55.  
    56.     public static BinaryFormatter GetBinaryFormatter()
    57.     {
    58.         BinaryFormatter formatter = new BinaryFormatter();
    59.  
    60.         return formatter;
    61.     }
    62.  
    63.  
    64. }
    65.  
    I get the error upon saving, the saving function is currently being called on a tester button I have, which calls
    Code (CSharp):
    1. Serialization.Save("Save", SaveData.data);
    I get no errors on load, but of course, there's probably no saved data to load because the save function doesn't work.

    For the record, I followed a tutorial for this, I don't fully understand binary formatters lol, but the tutorial was missing some important info so..

    Any help would be appreciated, thanks.

    Edit: Oh f, I just removed the monobehavior from my SaveData class, no more error, however, saving doesn't seem to work at all?
     
    Last edited: Dec 19, 2020
  2. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Now I'm getting this error upon saving...

    SerializationException: Type 'CoinProducerGM' in Assembly 'Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

    I don't understand, I'm not trying to save anything from the CoinProducerGM, CoinProducerGM is explicitly setting the data of the save data...

    This is my save function,

    Code (CSharp):
    1.     public void SaveGame()
    2.     {
    3.         Serialization.Save("Save", SaveData.data);
    4.     }
     
  3. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    The base class Monobehaviour is not flagged with the serializable attribute. Instead put the data you want saved in a custom class or data structure and serialize that instead. Or serialize all that data using one of Unity's Serializers, like JsonUtility

    Its for the best anyway, had Unity chose to allow Monobehaviour to be serializable, then you run the inevitable risk of your serialized binary effectively becoming corrupted anytime Unity updated their member declarations in the related classes when using a BinaryFormatter.
     
    Mashimaro7 likes this.
  4. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    I forgot to adjust my script above, I removed the monobehavior as it didn't need it anyway. Now, for some reason, it's saying it can't serialize some other class? here's my updated script
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5.  
    6. [Serializable]
    7. public class SaveData
    8. {
    9.     private static SaveData _data;
    10.  
    11.     public static SaveData data
    12.     {
    13.         get
    14.         {
    15.             if(_data == null)
    16.             {
    17.                 _data = new SaveData();
    18.             }
    19.  
    20.             return _data;
    21.         }
    22.         set
    23.         {
    24.             _data = value;
    25.         }
    26.     }
    27.  
    28.     public double currentCoins;
    29.     public int[] producerLevels;
    30.     public int[] upgradeLevels;
    31.     public int nuts;
    32.  
    33.    // public event Action onLoadGame;
    34.  
    35.     private void Awake()
    36.     {
    37.  
    38.         Load();
    39.     }
    40.  
    41.     private void OnApplicationQuit()
    42.     {
    43.         Save();
    44.     }
    45.  
    46.     public void Save()
    47.     {
    48.         Serialization.Save("Save", _data);
    49.     }
    50.  
    51.     public void Load()
    52.     {
    53.         if ((SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save") != null)
    54.         {
    55.             SaveData newData = (SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save");
    56.  
    57.             currentCoins = newData.currentCoins;
    58.             producerLevels = new int[4];
    59.  
    60.             for (int i = 0; i < producerLevels.Length; i++)
    61.             {
    62.                 producerLevels[i] = newData.producerLevels[i];
    63.             }
    64.             upgradeLevels = new int[4];
    65.  
    66.             for (int i = 0; i < upgradeLevels.Length; i++)
    67.             {
    68.                 upgradeLevels[i] = newData.upgradeLevels[i];
    69.             }
    70.  
    71.             nuts = newData.nuts;
    72.  
    73.             onLoadGame();
    74.         }
    75.     }
    76. }
    77.  
    Is it because of the action? I did a test and removed it, it seemed to save properly, but when I did that, it seems to not be loading the arrays properly, even in the above script. Is there some special way to load arrays? Also, how do I get my action working?
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Havyx and Mashimaro7 like this.
  6. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    I've got a similar system going on, but the classes I use as "save files" don't inherit Monobehaviour, they're plain old c# objects (I could be wrong but I think the binary formatter doesn't like classes that inherit other classes). An example on how I do it is, I'll have a Settings.cs script that does inherit Monobehaviour and save/loads an instance of another class called SettingsData with variables to save that doesn't inherit anything (marked with System.Serializeable). Hope that helps.
     
    Mashimaro7 likes this.
  7. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Oh, what is a more secure way of implementing serialization? Serialization is complicated lol

    Yeah, my bad, I noticed I had forgotten to remove the reference to monobehavior, I didn't need it anyway haha. But it still had the same error even when I didn't inherit from monobehavior, this time referencing another script, still not sure why...
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    In the year 2020 JSON is a favored approach because it produces text that you can easily debug.

    I recommend the JSON .NET package from the Unity asset store, which is free. The reason I recommend this is that the built-in Unity "tiny baby JSON" is completely useless for any type of real free-form data serialization, as it fails to handle many common types such as ... Dictionaries.

    The less you serialize, the easier it becomes. Select your saved data sparingly and you will save yourself a lot of trouble.
     
    laurentlavigne and Mashimaro7 like this.
  9. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Huh, and at first glance, at least, it looks a lot easier to work with.

    Yeah, I actually just reworked a big chunk of my code, so I'm really only serializing 2 int arrays, and int, and a double. It would've been a lot more before :p

    Thanks for the advice, I'll get to work on throwing my whole serialization script away and implementing JSON :)
     
  10. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    Have you tried deleting any save files from previous attempts. Sometimes when you change variables around (renaming, adding new ones etc) from your data class and then try loading from a save file with mismatched variables it throws an error.
     
    Kurt-Dekker likes this.
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    This ... with binary data, there's no formatting. With JSON you could pull up the file in a text editor and go "oh, I see, this is from a previous version of my save system..."
     
  12. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    I don't know much about JSON so I'm hoping for some clarification. If you have sensitive data like in-game currency, can it be editted if the user found the file? Is there a way to encrypt it? Only reason I've been chosing to use Binary Formatter is for the reason that it makes it harder for users to change values but it seems from the article Kurt-Dekker linked there other are vulnerabilies using it.
     
  13. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    I was serializing everything with binary formatter because it was an easy way to obfuscate the contents of the files, but thanks to this thread I switched part of it to json since I intend to receive save files from users and from what you guys have posted someone could send me modified files which could execute nefarious stuff just from deserializing the file. Anyway it was quite an easy switch just change the syntax a little.

    @Goest_ yeah in json its quite easy to modify that, anyone can just open it on notepad and change the currency to whatever they want
     
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Binary data can be edited exactly as easily.

    If any data is on an untrusted computer system (the user's computer is NOT trusted by you), then it can be edited. There is no point in further discussion. Trying to pretend like writing binary data makes it "unhackable" is a waste of your time.

    If you need it to be unhackable, the only chance you have is to make your own server and then REQUIRE the user to connect to your server.

    But remember, if the user wants he can always reverse engineer your server and make his own. Look at how many people have reverse engineered crazy complicated MMO worlds to see what I mean.

    Don't waste your time. Your biggest problem will be to even get people to play your game period. While I typed this there were probably 20 new games released on itch.io alone.
     
    Havyx and Mashimaro7 like this.
  15. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,315
    In my current projects, I am serializing everything to JSON because it's easy to implement and easily debuggable. To prevent super-easy rewriting on release builds, I'm encrypting the JSON with a key that's hardcoded inside the project. Obviously this doesn't pretend to be cryptographically secure, but it obfuscates it beyond what a binary format would do to prevent idle snooping. I believe this is essentially what Hollow Knight does, if you want a real world example of this technique.

    Odin Serializer also seems to boast a fast read/write speed for more complicated data structures, and has the ability to serialize down to binary rather than just JSON.
     
  16. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    I don't think this is entirely accurate if we're talking about your average user. I've tried opening a file created from a binary formatter and it is unintelligible to me. Sure they can edit it, but without knowing what they're doing when the binary formatter tries to deserialize the file it will throw an error rendering the save file corrupted.

    And I wasn't preteding that binary data is unhackable, I don't know where you got that assumption. I was just asking for clarification on if it's possible to encrypt JSON files.
     
    Phenotype likes this.
  17. Havyx

    Havyx

    Joined:
    Oct 20, 2020
    Posts:
    140
    I disagree with this statement. Opening a binary file is not straight forward (you certainly can't open it with notepad) and even then you have to know it's contents to correctly access the data. The only other option is to go through your memory and figure it out piece by piece which is super inconvenient.

    You are right in pointing out that the only way to truly keep data encrypted is to use a server and force the user to connect.

    The problem with the binary formatter is the contents are somewhat unknown. This acts as a double-edged sword because if someone replaces a binary file with malicious code and then sends it to you (maybe they report a bug and you ask for the save files to repro) you're going to have a bad time.

    This also applies to users who download hacks/cheats. If they download an unknown binary then you technically just helped a hacker execute an attack.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide

    "As a simpler analogy, assume that calling BinaryFormatter.Deserialize over a payload is the equivalent of interpreting that payload as a standalone executable and launching it."

    "
    Consider also an app that uses BinaryFormatter to persist save state. This might at first seem to be a safe scenario, as reading and writing data on your own hard drive represents a minor threat. However, sharing documents across email or the internet is common, and most end users wouldn't perceive opening these downloaded files as risky behavior.

    This scenario can be leveraged to nefarious effect. If the app is a game, users who share save files unknowingly place themselves at risk. The developers themselves can also be targeted. The attacker might email the developers' tech support, attaching a malicious data file and asking the support staff to open it. This kind of attack could give the attacker a foothold in the enterprise."

    BSON (Binary JSON) could be a useful alternative.

    or

    Binary Reader and BinaryWriter for XML and JSON

    https://docs.microsoft.com/en-us/dotnet/api/system.io.binaryreader
    https://docs.microsoft.com/en-us/dotnet/api/system.io.binarywriter


    NET offers several in-box serializers that can handle untrusted data safely:


    I guess there is also the argument that nobody is going to care about your game enough to create malicious binary files so using the binary formatter might be viable.

    You can try to mitigate the issue as seen here

    https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2301

    CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder

    Insecure deserializers are vulnerable when deserializing untrusted data. An attacker could modify the serialized data to include unexpected types to inject objects with malicious side effects. An attack against an insecure deserializer could, for example, execute commands on the underlying operating system, communicate over the network, or delete files.

    This rule finds System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialization method calls or references, when BinaryFormatter doesn't have its Binder set. If you want to disallow any deserialization with BinaryFormatter regardless of the Binder property, disable this rule and CA2302, and enable rule CA2300.
     
    Last edited: Dec 20, 2020
  18. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Binary formatter is not good for almost any use case. Its worst problem is that it can't be migrated easily. So if you change the object being formatted you need to keep multiple versions of the class for the binary formatter to be able to deserialize it.
     
  19. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    I dont feel responsible to safeguard the people who download hacks, since downloading a hack almost always entails a modified client or an injector ( which dont need to exploit my security flaws at all ), and they already assumed the risk themselves when they do it, so I wont bother to change the way I serialize assets and the like.

    However I do feel responsible for the trading of save slots, innocent actions that only involve the trading of 1 file, and should be secure, also for myself, I want to receive save files from users that help me correct bugs without fear of running insecure code.

    If people want to modify their save to give themselves 1million attack damage its fine, they could do the same using cheat engine aswell, and its a single player after all, its not like it will harm anyone.
     
  20. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Binary formatter only serializes the data the code running is the version that exists in your version of the game. So there is no insecure code running, though they could alter code paths in that code by altering the data being serialized. It requires the code to fubar to become unsecure though
     
  21. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    yes, I mean someone finds a bug, wants to show me, i ask for his save where the bug is clearly happening and i open it in my unity editor so I can take a look at what is going on, however secretly this person subverted the binary file and when I open it it is the same as if i was opening a .exe

    at least thats what i understood from this thread
     
  22. Havyx

    Havyx

    Joined:
    Oct 20, 2020
    Posts:
    140
    "BinaryFormatter was implemented before deserialization vulnerabilities were a well-understood threat category. As a result, the code does not follow modern best practices.

    The Deserialize method can be used as a vector for attackers to perform DoS attacks against consuming apps. These attacks might render the app unresponsive or result in unexpected process termination. This category of attack cannot be mitigated with a SerializationBinder or any other BinaryFormatter configuration switch. .NET considers this behavior to be by design and won't issue a code update to modify the behavior.

    BinaryFormatter.Deserialize may be vulnerable to other attack categories, such as information disclosure or remote code execution."

    upload_2020-12-20_12-14-43.png

    https://owasp.org/www-project-top-ten/2017/A8_2017-Insecure_Deserialization.html

    "The only safe architectural pattern is not to accept serialized objects from untrusted sources or to use serialization mediums that only permit primitive data types."
     
  23. AbandonedCrypt

    AbandonedCrypt

    Joined:
    Apr 13, 2019
    Posts:
    69
    I would recommend to recommend Odin Serializer which has been free for a while and, in contrast to JSON.NET, was built specifically for unity and can serialize everything JSON.NET can (and more) and it does it faster and more efficiently.

    Odin Serializer

    On the github page (linked) all necessary information about performance and compatibility are made very accessible.

    Additionally it has the benefit of making serialized data available to be viewed and edited in the inspector when using Odin Inspector.
     
    PhantasmicDev, Mashimaro7 and Havyx like this.
  24. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Well, I'll figure out how to get Odin Serializer after I can get JSON working lol, so I seem to have saving working, but it's not loading properly...

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using UnityEngine;
    5.  
    6. public class Serialization
    7. {
    8.  
    9.  
    10.     public static void Save(SaveData saveData)
    11.     {
    12.         if (!Directory.Exists(Application.dataPath + "/Saves/"))
    13.         {
    14.             Directory.CreateDirectory(Application.dataPath + "/Saves/");
    15.         }
    16.         SaveData data = new SaveData();
    17.         {
    18.             data.currentCoins = saveData.currentCoins;
    19.             data.upgradeLevels = saveData.upgradeLevels;
    20.             data.producerLevels = saveData.producerLevels;
    21.             data.nuts = saveData.nuts;
    22.         }
    23.         Debug.Log(data.currentCoins);
    24.         string json = JsonUtility.ToJson(saveData);
    25.         File.WriteAllText(Application.dataPath + "/Saves/save.json", json);
    26.      
    27.  
    28.     }
    29.  
    30.     public static bool Load()
    31.     {
    32.         string dataPath = File.ReadAllText(Application.dataPath + "/Saves/save.json");
    33.         if ((File.Exists(dataPath)))
    34.         {
    35.             SaveData saveData = JsonUtility.FromJson<SaveData>(dataPath);
    36.             return true;
    37.         }
    38.         return false;
    39.     }
    40.  
    41. }
    42.  
    Did I do something wrong?
    Code (CSharp):
    1.     public void Load()
    2.     {
    3.         Serialization.Load();
    4.         Debug.Log(currentCoins);
    5.         onLoadGame();
    6.     }
    Here in my SaveData class I have the current coins being printed, and an event calling the other classes to update... It just logs whatever the current value is and not the loaded value.
     
  25. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    remove this

    Code (CSharp):
    1. if ((File.Exists(dataPath)))
    also you have to do something after this

    Code (CSharp):
    1.  SaveData saveData = JsonUtility.FromJson<SaveData>(dataPath);
    2.          
    3. currentdata = saveData;
     
  26. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Well, now I changed it to
    Code (CSharp):
    1.     public static void Load()
    2.     {
    3.             SaveData data = JsonUtility.FromJson<SaveData>(dataPath + "save.json");
    4.             SaveData.data = data;
    5.     }
    And it's coming up JSON parse error, invalid value type?
     
  27. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    u didnt do ReadAllText

    also ur overwriting ur load
     
  28. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    723
    Oh okay, I think I got it working properly now, after fiddling around forever lol. Thanks!
     
  29. PhantasmicDev

    PhantasmicDev

    Joined:
    Jul 10, 2013
    Posts:
    35
    Oh my, I didn't know about this. Thanks for sharing!