Skip to content

Commit

Permalink
Harden reading settings file to prevent startup freeze
Browse files Browse the repository at this point in the history
There is an uncommon issue where settings file becomes corrupted and results in exception during iniParser.ReadFile(). This will cause game to freeze at startup on either a black screen or splash. Users can resolve by deleting settings.ini to restore defaults, but this usually requires community support.
This commit hardens settings impot in the following ways:
1. Create a settings backup file once successfully read.
2. Add exception handling for iniParser.ReadFile() to recover from corrupted settings file.
3. First fallback is to backup settings file.
4. Second fallback is to restore defaults automatically. This isn't ideal, but better than game freezing.
Currently unknown what causes settings.ini to become corrupted in this way. The repro sample provided by a user was filled with 5kb of "NULL" characters.
  • Loading branch information
Interkarma committed Oct 26, 2023
1 parent 46a65fd commit f3ed7fa
Showing 1 changed file with 49 additions and 20 deletions.
69 changes: 49 additions & 20 deletions Assets/Scripts/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class SettingsManager
{
const string defaultsIniName = "defaults.ini";
const string settingsIniName = "settings.ini";
const string settingsBakExt = ".bak";
const string settingsDistIniName = "settings-{0}.ini";

const string sectionDaggerfall = "Daggerfall";
Expand Down Expand Up @@ -718,15 +719,21 @@ public void SaveSettings()

#region Private Methods

string SettingsName()
string SettingsName(bool isBackup = false)
{
string name;
if (string.IsNullOrEmpty(DistributionSuffix))
return settingsIniName;
name = settingsIniName;
else
return string.Format(settingsDistIniName, DistributionSuffix);
name = string.Format(settingsDistIniName, DistributionSuffix);

if (isBackup)
name = Path.ChangeExtension(name, settingsBakExt);

return name;
}

void ReadSettingsFile()
void CreateDefaultSettingsFile(string userIniPath)
{
// Load defaults.ini
TextAsset asset = Resources.Load<TextAsset>(defaultsIniName);
Expand All @@ -735,43 +742,65 @@ void ReadSettingsFile()
defaultIniData = iniParser.ReadData(reader);
reader.Close();

// Create file
File.WriteAllBytes(userIniPath, asset.bytes);

Debug.LogFormat("Creating new '{0}' at path '{1}'", SettingsName(), userIniPath);
}

void ReadSettingsFile()
{
// Must have settings.ini in persistent data path
string message;
string userIniPath = Path.Combine(PersistentDataPath, SettingsName());
if (!File.Exists(userIniPath))
CreateDefaultSettingsFile(userIniPath);

// Load settings.ini and try to handle exception
// Failing to load settings at this stage might cause game to freeze at startup
// First try backup from last good startup then fallback to defaults
try
{
userIniData = iniParser.ReadFile(userIniPath);
}
catch (Exception)
{
// Create file
message = string.Format("Creating new '{0}' at path '{1}'", SettingsName(), userIniPath);
File.WriteAllBytes(userIniPath, asset.bytes);
DaggerfallUnity.LogMessage(message);
Debug.Log("Error parsing settings.ini. Trying backup file.");
string userIniBakPath = Path.Combine(PersistentDataPath, SettingsName(true));
try
{
userIniData = iniParser.ReadFile(userIniBakPath);
}
catch(Exception)
{
Debug.Log("Error parsing settings backup. Restoring defaults.");
CreateDefaultSettingsFile(userIniPath);
userIniData = iniParser.ReadFile(userIniPath);
}
}

// Log ini path in use
message = string.Format("Using '{0}' at path '{1}'", SettingsName(), userIniPath);
DaggerfallUnity.LogMessage(message);
Debug.LogFormat("Using '{0}' at path '{1}'", SettingsName(), userIniPath);

// Load settings.ini or set as read-only
userIniData = iniParser.ReadFile(userIniPath);
// Create a backup after successfully parsing ini data
WriteSettingsFile(true);

// Ensure user ini data in sync with default ini data
SyncIniData();
}

void WriteSettingsFile()
void WriteSettingsFile(bool isBackup = false)
{
string name = SettingsName(isBackup);
if (iniParser != null)
{
try
{
string path = Path.Combine(PersistentDataPath, SettingsName());
if (File.Exists(path))
{
iniParser.WriteFile(path, userIniData);
}
string path = Path.Combine(PersistentDataPath, name);
iniParser.WriteFile(path, userIniData);
}
catch
{
DaggerfallUnity.LogMessage("Failed to write settings.ini.");
Debug.LogFormat("Failed to write {0}", name);
}
}
}
Expand Down

0 comments on commit f3ed7fa

Please sign in to comment.