Skip to content

Commit

Permalink
Save enumeration crash fix and updates.
Browse files Browse the repository at this point in the history
* Fixes startup crashes during save enumeration from exceptions thrown while reading save files (mostly from corrupt saves).
* Unifies save enumerations regardless of which Flash directory is being populated, so they all behave the same.
* IO errors now get a message similar to permission errors.
  • Loading branch information
tmedwards committed Jul 31, 2015
1 parent a091819 commit 712cd36
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 62 deletions.
27 changes: 19 additions & 8 deletions CoCEd/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,27 @@ void Initialize()
VM.Create();

FileManager.BuildPaths();
var directories = FileManager.GetDirectories().ToArray(); // Load all on startup to check for errors
var directories = FileManager.GetDirectories().ToArray(); // Load all on startup to check for errors
var result = ExceptionBoxResult.Continue;
if (FileManager.PathWithMissingPermissions != null)
switch (FileManager.Result)
{
box = new ExceptionBox();
box.Title = "Could not scan some folders.";
box.Message = "CoCEd did not get permission to read a folder or a file.\nSome files won't be displayed in the open/save menus.";
box.Path = FileManager.PathWithMissingPermissions;
box.IsWarning = true;
result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue);
case FileEnumerationResult.NoPermission:
box = new ExceptionBox();
box.Title = "Could not scan some folders.";
box.Message = "CoCEd did not get permission to read a folder or file.\nSome files will not be displayed in the Open/Save menus.";
box.Path = FileManager.ResultPath;
box.IsWarning = true;
result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue);
break;

case FileEnumerationResult.Unreadable:
box = new ExceptionBox();
box.Title = "Could not read some folders.";
box.Message = "CoCEd could not read a folder or file.\nSome files will not be displayed in the Open/Save menus.";
box.Path = FileManager.ResultPath;
box.IsWarning = true;
result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue);
break;
}
if (result == ExceptionBoxResult.Quit)
{
Expand Down
69 changes: 40 additions & 29 deletions CoCEd/Model/AmfFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ namespace CoCEd.Model
{
public enum SerializationFormat
{
Slot = 0,
Exported = 1,
Slot,
Exported,
}


public sealed class AmfFile : AmfObject
{
static readonly HashSet<String> _backedUpFiles = new HashSet<string>();


public AmfFile(string path)
: base(AmfTypes.Array)
{
FilePath = path;
Name = Path.GetFileNameWithoutExtension(FilePath);
Date = File.GetLastWriteTime(path);
try
{
Expand All @@ -40,31 +39,15 @@ public AmfFile(string path)
}
}
}
#if !DEBUG
catch (IOException e)
{
Error = e.ToString();
}
catch (InvalidOperationException e)
{
Error = e.ToString();
}
catch (ArgumentException e)
{
Error = e.ToString();
}
catch (NotImplementedException e)
{
Error = e.ToString();
}
catch (UnauthorizedAccessException e)
// All exceptions need to be handled as the general case, since corrupt
// saves can also cause exceptions (e.g. InvalidCastException), however,
// we will flag permission and IO issues for consumers like FileManager
catch (Exception e)
{
Error = e.ToString();
}
#endif
catch (SecurityException e)
{
Error = e.ToString();
AmfFileError.Error type = AmfFileError.Error.Unknown;
if (e is SecurityException || e is UnauthorizedAccessException) type = AmfFileError.Error.NoPermission;
else if (e is IOException) type = AmfFileError.Error.Unreadable;
Error = new AmfFileError(type, e.ToString());
}
}

Expand All @@ -86,7 +69,7 @@ public string Name
private set;
}

public string Error
public AmfFileError Error
{
get;
private set;
Expand Down Expand Up @@ -185,4 +168,32 @@ public void TestSerialization()
}
#endif
}

public sealed class AmfFileError
{
public enum Error
{
Unknown,
NoPermission,
Unreadable,
}

public Error Type { get; private set; }
public string Mesg { get; private set; }

public AmfFileError(Error type, string mesg)
{
Type = type;
Mesg = mesg;
}
public AmfFileError(string mesg)
: this(Error.Unknown, mesg)
{
}

public override string ToString()
{
return Mesg;
}
}
}
69 changes: 49 additions & 20 deletions CoCEd/Model/FileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public enum DirectoryKind
Backup,
}

public enum FileEnumerationResult
{
Success,
NoPermission,
Unreadable,
}

public class FlashDirectory
{
public string Name;
Expand Down Expand Up @@ -49,7 +56,8 @@ public static int SaveSlotsUpperBoundByGame
get { return VM.Instance.Game.IsRevampMod ? MaxSaveSlotsRevampMod : MaxSaveSlotsCoC; }
}

public static string PathWithMissingPermissions { get; private set; }
public static FileEnumerationResult Result { get; private set; }
public static string ResultPath { get; private set; }

public static string BackupPath
{
Expand All @@ -62,6 +70,8 @@ public static string BackupPath

public static void BuildPaths()
{
Result = FileEnumerationResult.Success;

const string standardPath = @"Macromedia\Flash Player\#SharedObjects\";
const string chromePath1 = @"Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects\";
const string chromePath2 = @"Google\Chrome\User Data\Profile 1\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects\"; // Win 8/8.1 thing, apparently
Expand Down Expand Up @@ -127,14 +137,18 @@ static void BuildPath(string nameFormat, Environment.SpecialFolder root, string[
}
catch (SecurityException)
{
PathWithMissingPermissions = path;
Result = FileEnumerationResult.NoPermission;
ResultPath = path;
}
catch (UnauthorizedAccessException)
{
PathWithMissingPermissions = path;
Result = FileEnumerationResult.NoPermission;
ResultPath = path;
}
catch (IOException)
{
Result = FileEnumerationResult.Unreadable;
ResultPath = path;
}
}

Expand All @@ -152,14 +166,20 @@ public static FlashDirectory CreateBackupDirectory()
var dir = new FlashDirectory("Backup", BackupPath, true, DirectoryKind.Backup);

var dirInfo = new DirectoryInfo(BackupPath);
foreach (var file in dirInfo.GetFiles("*.bak").OrderByDescending(x => x.LastWriteTimeUtc)) dir.Files.Add(new AmfFile(file.FullName));
foreach (var filePath in dirInfo.GetFiles("*.bak").OrderByDescending(x => x.LastWriteTimeUtc).Select(x => x.FullName))
{
AddFileToDirectory(dir, filePath);
}
return dir;
}

static FlashDirectory CreateExternalDirectory()
{
var dir = new FlashDirectory("External", "", true, DirectoryKind.Backup);
foreach (var path in _externalPaths) dir.Files.Add(new AmfFile(path));
foreach (var filePath in _externalPaths)
{
AddFileToDirectory(dir, filePath);
}
return dir;
}

Expand All @@ -171,24 +191,33 @@ static FlashDirectory CreateDirectory(FlashDirectory dir)
for (int i = SaveSlotsLowerBound; i <= SaveSlotsUpperBound; i++)
{
var filePath = Path.Combine(dir.Path, "CoC_" + i + ".sol");
try
{
if (!File.Exists(filePath)) continue;
dir.Files.Add(new AmfFile(filePath));
}
catch (SecurityException)
{
PathWithMissingPermissions = filePath;
}
catch (UnauthorizedAccessException)
{
PathWithMissingPermissions = filePath;
}
catch (IOException)
AddFileToDirectory(dir, filePath);
}
return dir;
}

private static bool AddFileToDirectory(FlashDirectory dir, string filePath)
{
if (!File.Exists(filePath)) return false;

var amfFile = new AmfFile(filePath);
if (amfFile.Error != null)
{
switch (amfFile.Error.Type)
{
case AmfFileError.Error.NoPermission:
Result = FileEnumerationResult.NoPermission;
ResultPath = filePath;
return false;

case AmfFileError.Error.Unreadable:
Result = FileEnumerationResult.Unreadable;
ResultPath = filePath;
return false;
}
}
return dir;
dir.Files.Add(amfFile);
return true;
}

public static void TryRegisterExternalFile(string path)
Expand Down
2 changes: 1 addition & 1 deletion CoCEd/ViewModel/FilesVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ public Image Icon
{
get
{
if (String.IsNullOrEmpty(Source.Error)) return null;
if (Source.Error == null) return null;

BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
Expand Down
8 changes: 4 additions & 4 deletions CoCEd/ViewModel/VM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,17 @@ public void Load(string path, SerializationFormat expectedFormat, bool createBac
var file = new AmfFile(path);
var dataVersion = file.GetString("version");

if (!String.IsNullOrEmpty(file.Error))
if (file.Error != null)
{
var box = new ExceptionBox();
box.Title = "Could not scan some folders.";
box.Title = "Could not read file.";
box.Message = "CoCEd could not read this file correctly. Maybe it was corrupted or generated by an old version of Flash. Continuing may make CoCEd unstable or cause it to corrupt the file. It is strongly advised that you cancel this operation.";
box.Path = file.FilePath;
box.ExceptionMessage = file.Error;
box.ExceptionMessage = file.Error.Mesg;
box.IsWarning = true;
var result = box.ShowDialog(ExceptionBoxButtons.Continue, ExceptionBoxButtons.Cancel);

Logger.Error(file.Error);
Logger.Error(file.Error.Mesg);
if (result != ExceptionBoxResult.Continue) return;
}
else if (String.IsNullOrEmpty(dataVersion))
Expand Down

0 comments on commit 712cd36

Please sign in to comment.