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

Question BinaryReader loading in incorrect types -- how to check and verify?

Discussion in 'Scripting' started by BionicWombatGames, Jan 18, 2023.

  1. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    I'm using BinaryReader to handle my save data:

    Code (CSharp):
    1. using FileStream fileStream = File.Open(savePath, FileMode.Open);
    2. using BinaryReader binaryReader = new(fileStream);
    3. string jsonString = binaryReader.ReadString();
    4. Debug.Log("jsonString: " + jsonString);
    5. SaveData data = JsonUtility.FromJson<SaveData>(jsonString);
    Sometimes there is what I believe to be save file corruption, and one of the fields that is being loaded in is pointing to an incorrect asset. That is, I'm trying to load a Sprite, but Unity tells me it's Text, or some other random object.

    What I see in the console is, when trying
    portrait.texture = dialogSprite.texture
    "MissingReferenceException: The object of type 'Sprite' has been destroyed but you are still trying to access it."


    This is not because the Sprite is destroyed, but because, if I log this broken dialogSprite, I get: "Text (UnityEngine.GameObject)". (An uncorrupted version says "Sprite (UnityEngine.Sprite)". I believe it is text because the save file json jsonString is pointing to the incorrect asset:

    JsonString:
    Code (csharp):
    1. ..."portraitDialog":{"instanceID":34874}... //incorrect instanceID
    2. ..."portraitDialog":{"instanceID":30272}... //correct
    3. ..."portraitDialog":{"instanceID":30272}... //correct
    4.  
    OK, so, the save file is pointing to the wrong instance ID. Fine. Here is the question: how do I tell that it's the incorrect type? The answer should be GetType(), however GetType returns Sprite no matter what! Even when it's text. Using `if (x is Sprite)` also returns true. How do I get the actual underlying type that has been loaded so I can at least check if the data is corrupt before trying to use it?
     
  2. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    Here's some debug logging:

    Code (CSharp):
    1.  
    2. bool isS = customer.portraitDialog is Sprite;
    3. Debug.Log("isS: " + isS);
    4. Debug.Log("customer.portraitDialog.name: " + customer.portraitDialog.name);
    5. Debug.Log("customer.portraitDialog: " + customer.portraitDialog.ToString() + " | customer.portraitDialog.GetType() : " + customer.portraitDialog.GetType() +
    6.   " | customer.potraitDialog.GetHashCode(): " + customer.portraitDialog.GetHashCode());
    7.  
    8. //Output, bad object:
    9. //isS: True
    10. //customer.portraitDialog.name: Text
    11. //customer.portraitDialog: Text (UnityEngine.GameObject) | customer.portraitDialog.GetType() : UnityEngine.Sprite | customer.potraitDialog.GetHashCode(): 34874
    12.  
    13. //Output, good object:
    14. //isS: True
    15. //customer.portraitDialog.name: Characters-Thumbnails
    16. //customer.portraitDialog: Characters-Thumbnails (UnityEngine.Sprite) | //customer.portraitDialog.GetType() : UnityEngine.Sprite | customer.potraitDialog.GetHashCode(): 30272
     
  3. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    GetType
    tells you the type of a C# object.

    It doesn't matter that you're actually pointing at a bogus asset: as far as C# is concerned, that's a Sprite!
     
  4. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    OK, so how do I tell what it actually is? *Something* knows what it is, because ToString tells me it's a GameObject and not a Sprite. Where does ToString get that from?
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,895
    Just as an FYI... InstanceID's can't be used to save/load objects across editor and runtime sessions. Especially in build they are not persistent between sessions, so you will probably want to rethink your save system as it's already headed for a dead-end.
     
  6. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,531
    Well, your thread mixes up several kinda unrelated topics. First of all your use of the BinaryReader has nothing to do with your problem as you're just reading a string which containst json. So where you get that string from doesn't really matter.

    Your next and biggest issue is that you can not use the instance id across sessions as they are only valid for one session and can change between sessions. They are mainly used by Unity internally and are consistent inside their own format, but can actually change between two saves. Unity has really limited Asset reference capabilities. Unity has a package called Addressables which try to solve some of those issues by allowing to give assets an "address" that can be used across sessions.

    Other options are of course that you roll your own system to reference and identify assets. It highly depends on your needs.
     
  8. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    Thanks, that's really useful, I'll definitely address the instance IDs, they don't even really need to be saved anyway. I guess the question still remains, though, that when this happens, how do you check it?
     
  9. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    I don't think you should. You're basically throwing a dart at a list of Objects. Checking if you hit the right kind of Object would technically catch most of the incorrect results, but who's to say you hit the right Sprite?

    You should never be in this situation in the first place!
     
  10. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    Yes I shouldn't do it, and I'm not going to do it, but how would I do it regardless? What introspection is ToString() using? It is still a valuable question to have an answer to so if I (or someone else) runs into type clashes in the future we'll at least have some ideas.
     
  11. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    Just to be clear, if I drag an asset reference into the inspector into a field of a serialized object, that asset reference is safe, right? It's just when I try to write and read it from disk that it can break?
     
  12. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,531
    Yes. Asset references setup in the editor actually uses the asset GUID that every asset gets. It's stored in the .meta file. Unfortunately Unity doesn't provide any means to use or access this ID at runtime at the moment. Note that most assets in Unity that are not imported from outside are usually serialized as YAML text (scene files, prefabs, materials, etc) so you can easily view them in a text editor. Unity has even partially documented the format. The documentation has a section with a few pages about that format.

    As I said, the whole asset reference thing is mainly a think of the editor and game authoring in the editor and asset database. Unity does not really provide any means to serialize asset references at runtime. Addressables try to fill that gap, at least partially.

    So actual serialized asset references (which can only be setup in the editor) use the GUID under the hood which you can't access or resolve at runtime yourself. The instanceID is a session unique ID that identifies any UnityEngine.Object derived object and mainly acts as the glue between the managed C# world and the native C++ code of Unity. The instance ID can be kind of treated like the temporary "address". The fact that Unity's JsonUtility does serialize out the instance ID helps deserializing a perviously serialized reference to a UnityEngine.Object, but that is only valid within the same session. Even a hot reload could actually change them. So you generally should not store them anywhere. The instance id is mainly an internal ID for Unity itself.

    I'm not sure what exactly you're refering to here. What exactly do you mean here? You shouldn't store the instance ID because it is not a reliable reference between sessions. So what exactly do you want to do regardless? If it's using the instance ID as a cross-session reference, you simply can't. Imagine you have a window in a building on the 23th floor. There may be a 50% chance that there's a window cleaning lift just outside the window, but you have no way of knowing if it's there or not. So your question is essentially you really want to jump out of the window regardless of knowing if it's there to save you or not. The solution is to not rely on that lift. If you really want to jump out the window, find something different, maybe a parachute. Back to Unity that would mean if you want to permanentally serialize a reference to an asset at runtime, you have to create your own ID system or use addressables or something like that. It all depends on what exact problem you want to solve. As I said, there's no build-in way to serialize and reload asset references at runtime.
     
  13. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    Thanks for the info on asset references, it makes a lot of sense. I wasn't even really storing the sprite reference intentionally, it was part of a serialized data struct that got written into my json and it seemed to be working so I never really thought about it. It was trivial to remove and just look up the sprite from the data structure.

    Oh, I'm just wondering how to get the corrupted type of the misread object in the original code. Like, Unity is reconstructing this object from the json and sticking a random asset reference into what is supposed to be a Sprite, and when I do GetType it says Sprite but when I do ToString it says "Text (UnityEngine.GameObject)" (which is what the random asset actually is), I just wanted to know how to get Unity to spit out that GameObject type from some (for me unknown) function. I come from Objective-C land where objects can happily reside as the incorrect type like this, but in Objective-C you can actually recover it if that happens (whether or not that is a wise idea is a whole other discussion).
     
  14. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    I poked around a bit, but UnityEngine.Object.ToString just calls a native ToString method.
     
    BionicWombatGames likes this.
  15. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    And from what I could find, the native C# ToString method just uses GetType(), but the ToString is still showing a different type than my calling GetType, so it's very confusing.
     
  16. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    I do not believe that it is hitting the default ToString method for a C# object:

    Code (CSharp):
    1. [MethodImpl(MethodImplOptions.InternalCall)]
    2. [FreeFunction("UnityEngineObjectBindings::ToString")]
    3. private static extern string ToString(Object obj);
    I didn't find anything when searching up that UnityEngineObjectBindings tag -- I suppose that's internal to Unity, and not something that is publicly available :p