Help me, this bug is making me struggle(and idk where is coming from): System.NullReferenceException

Godot Version

v4.2.2.stable.mono.official [15073afe3]

Question

Someone help me on the System.NullReferenceException error
I added conditions to check saves, and the input of the user(because this error is in a popup and it’s a runtime error), and all of that was to add a password that was exclusive for the user that could be stored on a pendrive, your HDD/SSD, and maybe this is related to the GameData and SaveSystem scripts.

Here’s the scripts:

// GameInit.cs(MainMenu Scene node)
using Godot;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using SystemFileAccess = System.IO.FileAccess;
using GodotFileAccess = Godot.FileAccess;

public partial class GameInit : Control
{
	private PopupMenu _popupMenu;
	public SaveSystem saveSystem = new SaveSystem();
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		var saveData = saveSystem.LoadGame();
		var DataExists = saveData["exists"];
		if ((bool)DataExists != true) {
			GameData gameDataClass = new GameData();
			saveData = gameDataClass.MakeSave();
		}
		
		this.SetMeta("SaveData", saveData);
		
		var hashedPassword = saveData["hashedPassword"];
		if ((string)hashedPassword == "") {
			DriveInfo[] drives = DriveInfo.GetDrives();
			_popupMenu = GetNode<PopupMenu>("PopupMenu");
			
			var i = 0;
			foreach (DriveInfo drive in drives)
			{
				// The drive is ready?
				if (drive.IsReady)
				{
					// Only Removable and Fixed Drives
					if (drive.DriveType == DriveType.Removable || drive.DriveType == DriveType.Fixed)
					{
						_popupMenu.AddItem(drive.RootDirectory.FullName, i);
					}
					i++;
				}
			}
			
			Callable callable = new Callable(this, "ChosenDrive");
			_popupMenu.Connect("id_pressed", callable);
			_popupMenu.PopupCentered();
		}
	}
	
	public void ChosenDrive(int id) {
		var saveData = (Godot.Collections.Dictionary<string, Godot.Variant>)this.GetMeta("SaveData");
		if (saveData == null) {
			GD.PrintErr("A problem ocurred when trying to create save");
			return;
		}
		string drive = _popupMenu.GetItemText(id);
		if (string.IsNullOrEmpty(drive)) {
			GD.PrintErr("Drive is null or empty.");
			return;
		}
		string guid = Guid.NewGuid().ToString();
		saveData["selectedDrive"] = drive;
		var fullPath = System.IO.Path.Combine(drive, "password.guid");
		using var driveFile = GodotFileAccess.Open(fullPath, GodotFileAccess.ModeFlags.Write);
		saveData["hashedPassword"] = GenerateSHA256Hash(guid);
		driveFile.StoreLine(guid);
		saveSystem.SaveGame(saveData);
		this.SetMeta("DevAuthenticated", true);
		this.SetMeta("SaveData", saveData);
	}
	
	private string GenerateSHA256Hash(string input)
	{
		using (SHA256 sha256 = SHA256.Create())
		{
			byte[] bytes = Encoding.UTF8.GetBytes(input);
			byte[] hashBytes = sha256.ComputeHash(bytes);

			// Converte o hash para uma string hexadecimal
			StringBuilder hashStringBuilder = new StringBuilder();
			foreach (byte b in hashBytes)
			{
				hashStringBuilder.Append(b.ToString("x2"));
			}

			return hashStringBuilder.ToString();
		}
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}
}
// SaveSystem.cs
using Godot;
using System;
using System.IO;
using SystemFileAccess = System.IO.FileAccess;
using GodotFileAccess = Godot.FileAccess;

public partial class SaveSystem : Node
{
	public string fullPath;
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		string executablePath = OS.GetExecutablePath();
		string directoryPath = System.IO.Path.GetDirectoryName(executablePath);
		fullPath = System.IO.Path.Combine(directoryPath, "savegame.save");
	}
	
	public void SaveGame(Godot.Collections.Dictionary<string, Variant> gameData)
	{
		using var saveFile = GodotFileAccess.Open(fullPath, GodotFileAccess.ModeFlags.Write);

		var jsonString = Json.Stringify(gameData);
		saveFile.StoreLine(jsonString);
	}
	
	// Note: This can be called from anywhere inside the tree. This function is
	// path independent.
	public Godot.Collections.Dictionary<string, Variant> LoadGame()
	{
		if (!GodotFileAccess.FileExists(fullPath))
		{
			return new Godot.Collections.Dictionary<string, Variant>() 
			{
				{ "exists", false }
			}; // Error! We don't have a save to load.
		}

		// Load the file line and process that dictionary to get the data
		// it represents.
		using var saveFile = GodotFileAccess.Open(fullPath, GodotFileAccess.ModeFlags.Read);
		var jsonString = saveFile.GetLine();
		var json = new Json();
		var parseResult = json.Parse(jsonString);
		if (parseResult != Error.Ok)
		{
			GD.Print($"An error ocurred while parsing the data: {json.GetErrorMessage()}, for confirming check your savegame.save file");
			return new Godot.Collections.Dictionary<string, Variant>()
			{
				{ "exists", false }
			};
		}
		
		var saveData = new Godot.Collections.Dictionary<string, Variant>((Godot.Collections.Dictionary)json.Data);
		return saveData;
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}
}
// GameData.cs
using Godot;
using System;

public partial class GameData : Node
{
	public Godot.Collections.Dictionary<string, Variant> MakeSave()
	{
		var mainMenuThemeVidDic = new Godot.Collections.Dictionary<string, Variant>()
		{
			{ "sonicEntry", "" },
			{ "sonicFingerMoving", "" }
		};
		
		Vector2 emptyActSize = new Vector2(100, 200);
		var emptyActTilemap = new Godot.Collections.Array();
		var emptyAct = new Godot.Collections.Dictionary<string, Variant>()
		{
			{ "Name", "Empty" },
			{ "ZoneName", "Act" },
			{ "Act", 0 },
			{ "tilemapSize", emptyActSize },
			{ "tilemap", emptyActTilemap }
		};
		
		var levelsArray = new Godot.Collections.Array()
		{
			emptyAct
		};
		
		return new Godot.Collections.Dictionary<string, Variant>()
		{
			{ "mainMenuThemeVid", mainMenuThemeVidDic },
			{ "levels", levelsArray },
			{ "hashedPassword", "" },
			{ "selectedDrive", "" },
			{ "exists", true }
		};
	}
}

. There are only 4 scripts in my project but the 4th one still has nothing, things will be put later.

Can you post the full error? That way people can help you better

1 Like

Yes! Here’s the full error:

E 0:00:13:0606   void GameInit.ChosenDrive(int): System.NullReferenceException: Object reference not set to an instance of an object.
  <Erro C#>      System.NullReferenceException
  <Origem C#>    GameInit.cs:72 @ void GameInit.ChosenDrive(int)
  <Rastreamento de Pilha>GameInit.cs:72 @ void GameInit.ChosenDrive(int)
                 GameInit_ScriptMethods.generated.cs:54 @ bool GameInit.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

This line is 72, which is where the NullReferenceException happens:
string drive = _popupMenu.GetItemText(id);
This means that your _popupMenu is null.
And you do the following:
_popupMenu = GetNode<PopupMenu>("PopupMenu");

So either this node name is incorrect, or it is somewhere else in your scene hierarchy.
If your _popupMenu is a child of the GameInit node, then I would use
[Export] private PopupMenu _popupMenu; instead then set it in the inspector rather than a GetNode

Can you post a screenshot of the hierarchy?

2 Likes

It was suspect, trying to solve this error i tried to comment the drive list and add a option for adding password, it kept saying that the string is empty. But i tried it and it didn’t show up on the inspector

What didnt show up, do you mean the [Export] variable?
You first need to build, in order for it to show up.

I builded it didn’t show up
Capturarka999
The script is where the blue arrow is pointing at, and it loads the PopupMenu and the PopupPanel

Did you save the script then?

Also can you post a screenshot of the inspector tab?

I tried to build i didn’t save the file, i saved it i tried to build again it didn’t show up
UPDATE while making this reply: suddenly it did show up
ANOTHER UPDATE: The error continues but in the line 71, but i implemented a random password button, but…:

E 0:00:18:0395   void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant]): System.NullReferenceException: Object reference not set to an instance of an object.
  <Erro C#>      System.NullReferenceException
  <Origem C#>    SaveSystem.cs:24 @ void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant])
  <Rastreamento de Pilha>SaveSystem.cs:24 @ void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant])
                 GameInit.cs:79 @ void GameInit.ChosenDrive(int)
                 GameInit_ScriptMethods.generated.cs:54 @ bool GameInit.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

Another error ocurred now in saveSystem, and the main error continues,
also the updated GameInit Script:

using Godot;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using SystemFileAccess = System.IO.FileAccess;
using GodotFileAccess = Godot.FileAccess;

public partial class GameInit : Control
{
	[Export] public PopupMenu _popupMenu;
	[Export] public PopupPanel _popupPanel;
	public SaveSystem saveSystem = new SaveSystem();
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		var saveData = saveSystem.LoadGame();
		var DataExists = saveData["exists"];
		if ((bool)DataExists != true) {
			GameData gameDataClass = new GameData();
			saveData = gameDataClass.MakeSave();
		}
		
		this.SetMeta("SaveData", saveData);
		
		var hashedPassword = saveData["hashedPassword"];
		if ((string)hashedPassword == "") {
			DriveInfo[] drives = DriveInfo.GetDrives();
			
			var i = 0;
			foreach (DriveInfo drive in drives)
			{
				// The drive is ready?
				if (drive.IsReady)
				{
					// Only Removable and Fixed Drives
					if (drive.DriveType == DriveType.Removable || drive.DriveType == DriveType.Fixed)
					{
						// Not implemented yet
						_popupMenu.AddItem(drive.RootDirectory.FullName, i);
					}
					i++;
				}
			}
			_popupMenu.AddItem("Get Text Password", i);
			
			Callable callable = new Callable(this, "ChosenDrive");
			_popupMenu.Connect("id_pressed", callable);
			_popupMenu.PopupCentered();
		}
	}
	
	public void ChosenDrive(int id) {
		var saveData = (Godot.Collections.Dictionary<string, Godot.Variant>)this.GetMeta("SaveData");
		if (saveData == null) {
			GD.PrintErr("A problem ocurred when trying to create save");
			return;
		}
		string drive = _popupMenu.GetItemText(id);
		if (drive == null || drive == "") {
			GD.PrintErr("Drive is null or empty.");
			return;
		}
		if (drive != "Get Text Password") {
			string guid = Guid.NewGuid().ToString();
			saveData["selectedDrive"] = drive;
			var fullPath = System.IO.Path.Combine(drive, "password.guid");
			using var driveFile = GodotFileAccess.Open(fullPath, GodotFileAccess.ModeFlags.Write);
			saveData["hashedPassword"] = GenerateSHA256Hash(guid);
			driveFile.StoreLine(guid);	
		} else {
			string guid = Guid.NewGuid().ToString();
			saveData["hashedPassword"] = GenerateSHA256Hash(guid);
			Label popupLabel = _popupPanel.GetNode<Label>("Label");
			popupLabel.Set("Text", "Password: " + guid);
			_popupPanel.PopupCentered();
		}
		saveSystem.SaveGame(saveData);
		this.SetMeta("DevAuthenticated", true);
		this.SetMeta("SaveData", saveData);
	}
	
	private string GenerateSHA256Hash(string input)
	{
		using (SHA256 sha256 = SHA256.Create())
		{
			byte[] bytes = Encoding.UTF8.GetBytes(input);
			byte[] hashBytes = sha256.ComputeHash(bytes);

			StringBuilder hashStringBuilder = new StringBuilder();
			foreach (byte b in hashBytes)
			{
				hashStringBuilder.Append(b.ToString("x2"));
			}

			return hashStringBuilder.ToString();
		}
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}
}

And also you did help me, because when clicking this button it would display “Drive is null or empty”, but now is this error.
UPDATE: maybe i should make another topic about this new error?

We can finish this in here. Can you remove the solution for now?

I looked at the error and it says that SaveSystem.cs:24 is the problem.

Meaning line 24 of the SaveSystem class.
From the first comment you made, it seems to be this line for me:
saveFile.StoreLine(jsonString);

Meaning either saveFile is wrong, or the jsonString is invalid. Try to print out both to see which one is the error and, you will have to look how to fix that.

Or if you updated your SaveSystem class, post line 24. Then we can look at it together

1 Like

Looks like the problem root starts at the lines 14-16, the jsonString was being returned:

Godot Engine v4.2.2.stable.mono.official.15073afe3 - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 391.35 - Compatibility - Using Device: NVIDIA - GeForce GT 730
 

{"bonusStages":[{"Act":0,"LevelPackedScene":"res://EmptyAct.tscn","Name":"Empty","ZoneName":"Act"}],"exists":true,"hashedPassword":"3e444859b71d6e6d23fd69aa0227ccd9f48492a3f20277bf23e519fc0bae2974","levels":[{"Act":0,"LevelPackedScene":"res://EmptyAct.tscn","Name":"Empty","ZoneName":"Act"}],"mainMenuThemeVid":{"sonicEntry":"","sonicFingerMoving":""},"selectedDrive":"","specialStage":{"Act":0,"LevelPackedScene":"res://EmptyAct.tscn","Name":"Empty","ZoneName":"Act"}}

(game Data), but i put a print function before that to print the fullPath, and it showed nothing. It was almost impossible that the saveFile was invalid and the fullPath not, the line to save the file was taken from documentation

if fullPath is empty, that gives you your solution then. You have to check why that is empty, and if you can create the fullPath again, just create an if condition checking if fullPath is empty.

It’s a scope problem:

CS0103: O nome "executablePath" não existe no contexto atual C:\Users\user\Desktop\SonicDK\Scripts\SaveSystem.cs(24,15)

I tried to print the path to the executable but it was defined only in Ready() function, while the fullPath was being set in the Ready() function but created in the class itself:

public string fullPath;
	
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		string executablePath = OS.GetExecutablePath();
		string directoryPath = System.IO.Path.GetDirectoryName(executablePath);
		fullPath = System.IO.Path.Combine(directoryPath, "savegame.save");
	}

Now the error is in the line 29 it hasn’t ended

You can either make the variable go in the class scope instead of ready or print: OS.GetExecutablePath() in the other method where the problem occurs.

Also if you want to save godot recommends you do it in the user directory.

Actually, I’ve been trying to make it more easy to make sonic fangames so I’ve thinked, If i create a base that can be edited trough godot or another app, and you don’t need coding for that, i will add a trigger system, and also I did it and the error in line 29 continued, actually i solved that one in line 24 then i got a error in line 29, The error:

E 0:00:04:0402   void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant]): System.NullReferenceException: Object reference not set to an instance of an object.
  <Erro C#>      System.NullReferenceException
  <Origem C#>    SaveSystem.cs:29 @ void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant])
  <Rastreamento de Pilha>SaveSystem.cs:29 @ void SaveSystem.SaveGame(Godot.Collections.Dictionary`2[System.String,Godot.Variant])
                 GameInit.cs:79 @ void GameInit.ChosenDrive(int)
                 GameInit_ScriptMethods.generated.cs:54 @ bool GameInit.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

Okay, by now you should know what to do when a System.NullReferenceException happens.

You look at the stack trace, see the first time you see your classname.cs:XX

Then you go to line XX and see what is null by printing, Like so: `GD.Print($“{variable == null}”);

After that you check on how to solve it.

Also what do you mean by this:

Actually, I’ve been trying to make it more easy to make sonic fangames so I’ve thinked, If i create a base that can be edited trough godot or another app, and you don’t need coding for that, i will add a trigger system.

To make such a system you will need to code a LOT, more than you need to do now, so I wont recommend this