Error With Binary Formatter

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,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class SaveData : MonoBehaviour
{
    private static SaveData _data;

    public static SaveData data
    {
        get
        {
            if(_data == null)
            {
                _data = new SaveData();
            }

            return _data;
        }
    }

    public double currentCoins;
    public int[] producerLevels;
    public int[] upgradeLevels;
    public int nuts;

    private void Awake()
    {
        Load();
    }

    private void OnApplicationQuit()
    {
        Serialization.Save("Save", data);
    }

    public void Load()
    {
        _data = (SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save");
    }
}

The variables are being assigned in the scripts, also here’s my serialization script,

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class Serialization
{



    public static bool Save(string saveName,object saveData)
    {
        BinaryFormatter formatter = GetBinaryFormatter();

        if(!Directory.Exists(Application.persistentDataPath + "/saves"))
        {
            Directory.CreateDirectory(Application.persistentDataPath + "/saves");
        }

        string path = Application.persistentDataPath + "/saves" + saveName + ".save";

        FileStream file = File.Create(path);
        formatter.Serialize(file,saveData);

        file.Close();

        return false;
    }

    public static object Load(string path)
    {
        if (!File.Exists(path))
        {
            return null;
        }

        BinaryFormatter formatter = GetBinaryFormatter();

        FileStream file = File.Open(path,FileMode.Open);

        try
        {
            object save = formatter.Deserialize(file);
            file.Close();
            return save;
        }
        catch
        {
            Debug.LogErrorFormat("Failed to load file at {0}", path);
            file.Close();
            return null;
        }
    }

    public static BinaryFormatter GetBinaryFormatter()
    {
        BinaryFormatter formatter = new BinaryFormatter();

        return formatter;
    }


}

I get the error upon saving, the saving function is currently being called on a tester button I have, which calls

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?

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,

    public void SaveGame()
    {
        Serialization.Save("Save", SaveData.data);
    }

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.

1 Like

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

[Serializable]
public class SaveData
{
    private static SaveData _data;

    public static SaveData data
    {
        get
        {
            if(_data == null)
            {
                _data = new SaveData();
            }

            return _data;
        }
        set
        {
            _data = value;
        }
    }

    public double currentCoins;
    public int[] producerLevels;
    public int[] upgradeLevels;
    public int nuts;

   // public event Action onLoadGame;

    private void Awake()
    {

        Load();
    }

    private void OnApplicationQuit()
    {
        Save();
    }

    public void Save()
    {
        Serialization.Save("Save", _data);
    }

    public void Load()
    {
        if ((SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save") != null)
        {
            SaveData newData = (SaveData)Serialization.Load(Application.persistentDataPath + "/saves/Save.save");

            currentCoins = newData.currentCoins;
            producerLevels = new int[4];

            for (int i = 0; i < producerLevels.Length; i++)
            {
                producerLevels[i] = newData.producerLevels[i];
            }
            upgradeLevels = new int[4];

            for (int i = 0; i < upgradeLevels.Length; i++)
            {
                upgradeLevels[i] = newData.upgradeLevels[i];
            }

            nuts = newData.nuts;

            onLoadGame();
        }
    }
}

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?

Don’t use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

2 Likes

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.

1 Like

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…

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.

2 Likes

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 :stuck_out_tongue:

Thanks for the advice, I’ll get to work on throwing my whole serialization script away and implementing JSON :slight_smile:

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.

1 Like

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…”

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.

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.

@PhantasmicDev yeah in json its quite easy to modify that, anyone can just open it on notepad and change the currency to whatever they want

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.

2 Likes

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.

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.

1 Like

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.

“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.

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.

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.

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