Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Display English exception in ExceptionBox #343

Open
wants to merge 10 commits into
base: 1.0.6.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions FrostyEditor/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,18 @@ private void App_DispatcherUnhandledException(object sender, System.Windows.Thre
if (project.IsDirty)
{
string name = project.DisplayName.Replace(".fbproject", "");
DateTime timeStamp = DateTime.Now;

project.Filename = "Autosave/" + name + "_" + timeStamp.Day.ToString("D2") + timeStamp.Month.ToString("D2") + timeStamp.Year.ToString("D4") + "_" + timeStamp.Hour.ToString("D2") + timeStamp.Minute.ToString("D2") + timeStamp.Second.ToString("D2") + ".fbproject";
project.Filename = "Autosave/" + name + "_" + DateTime.Now.ToString("ddMMyyyy_HHmmss") + ".fbproject";
project.Save();
}
}

Exception exp = e.Exception;
using (NativeWriter writer = new NativeWriter(new FileStream("crashlog.txt", FileMode.Create)))
writer.WriteLine($"{exp.Message}\r\n\r\n{exp.StackTrace}");

FrostyExceptionBox.Show(exp, "Frosty Editor");
Environment.Exit(0);
if (FrostyExceptionBox.Show(exp, "Frosty Editor") == MessageBoxResult.Cancel)
App.Logger.LogWarning("Exception ignored, unknown error may occur");
else
Environment.Exit(-1);
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
Expand Down
9 changes: 4 additions & 5 deletions FrostyModManager/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,10 @@ private void App_DispatcherUnhandledException(object sender, System.Windows.Thre
{
Exception exp = e.Exception;

using (NativeWriter writer = new NativeWriter(new FileStream("crashlog.txt", FileMode.Create)))
writer.WriteLine($"{exp.Message}\r\n\r\n{exp.StackTrace}");

FrostyExceptionBox.Show(exp, "Frosty Mod Manager");
Environment.Exit(0);
if (FrostyExceptionBox.Show(exp, "Frosty Mod Manager") == MessageBoxResult.Cancel)
App.Logger.LogWarning("Exception ignored, unknown error may occur");
else
Environment.Exit(-1);
}

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
Expand Down
196 changes: 190 additions & 6 deletions FrostyPlugin/Controls/FrostyExceptionBox.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
using Frosty.Controls;
using Frosty.Core.Windows;
using System;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace Frosty.Core.Controls
{
/// <summary>
/// Handle button click
/// </summary>
public class ExceptionBoxClickCommand : System.Windows.Input.ICommand
{
public event EventHandler CanExecuteChanged {
add => System.Windows.Input.CommandManager.RequerySuggested += value;
remove => System.Windows.Input.CommandManager.RequerySuggested -= value;
}

public bool CanExecute(object parameter)
{
return true;
}

public void Execute(object parameter)
{
System.Windows.Controls.Button btn = parameter as System.Windows.Controls.Button;
FrostyExceptionBox parentWin = Window.GetWindow(btn) as FrostyExceptionBox;

string buttonName = btn.Name;

if (buttonName == "PART_CopyExceptionButton")
{
Clipboard.SetText(parentWin.ExceptionText);
Clipboard.Flush();
}
else if (buttonName == "PART_CopyLogButton")
{
Clipboard.SetText(parentWin.LogText);
Clipboard.Flush();
}
else if (buttonName == "PART_IgnoreButton")
{
parentWin.isIgnored = true;
parentWin.Close();
}
}
}

public class FrostyExceptionBox : FrostyDockableWindow
{
#region -- Properties --

#region -- ExceptionText --
public bool isIgnored { get; internal set; } = false;

#region -- Text --
public static readonly DependencyProperty ExceptionTextProperty = DependencyProperty.Register("ExceptionText", typeof(string), typeof(FrostyExceptionBox), new PropertyMetadata(""));
public string ExceptionText
{
get => (string)GetValue(ExceptionTextProperty);
set => SetValue(ExceptionTextProperty, value);
}

public static readonly DependencyProperty ExceptionMessageTextProperty = DependencyProperty.Register("ExceptionMessageText", typeof(string), typeof(FrostyExceptionBox), new PropertyMetadata(""));
public string ExceptionMessageText {
get => (string)GetValue(ExceptionMessageTextProperty);
set => SetValue(ExceptionMessageTextProperty, value);
}

public static readonly DependencyProperty LogTextProperty = DependencyProperty.Register("LogText", typeof(string), typeof(FrostyExceptionBox), new PropertyMetadata(""));
public string LogText {
get => (string)GetValue(LogTextProperty);
set => SetValue(LogTextProperty, value);
}
#endregion

#endregion
Expand Down Expand Up @@ -49,15 +105,143 @@ public override void OnApplyTemplate()
base.OnApplyTemplate();
}

public static MessageBoxResult Show(Exception e, string title)
public static MessageBoxResult Show(Exception e, string title = "Frosty Toolsuite")
{
FrostyExceptionBox window = new FrostyExceptionBox
{
Title = title,
ExceptionText = e.Message + "\n\n" + e.StackTrace
ExceptionText = UnlocalizeException(e),
LogText = (App.Logger as FrostyCore.FrostyLogger).LogText,
ExceptionMessageText = e.Message
};

return (window.ShowDialog() == true) ? MessageBoxResult.OK : MessageBoxResult.Cancel;
// Write crash log
try
{
Directory.CreateDirectory($"{Environment.CurrentDirectory}\\CrashLogs");
using (StreamWriter writer = new StreamWriter(new FileStream($"{Environment.CurrentDirectory}\\CrashLogs\\{DateTime.Now.ToString("ddMMyyyy_HHmmss")}_{e.Source}.txt", FileMode.Create)))
{
writer.WriteLine(window.ExceptionText);
writer.WriteLine("Log:");
writer.Write(window.LogText);
}
}
catch (IOException) // Directory maybe in use
{
using (StreamWriter writer = new StreamWriter(new FileStream($"crashlog_{DateTime.Now.ToString("ddMMyyyy_HHmmss")}.txt", FileMode.Create)))
{
writer.WriteLine(window.ExceptionText);
writer.WriteLine("Log:");
writer.Write(window.LogText);
}
}
catch
{
App.Logger.LogError("Failed to write crash log");
}


window.ShowDialog();

// Return MessageBoxResult.Cancel if user click Ignore
if (window.isIgnored) return MessageBoxResult.Cancel;
return (window.DialogResult == true) ? MessageBoxResult.OK : MessageBoxResult.No;
}

/// <summary>
/// Generate exception message in English (if possible)
/// </summary>
private static string UnlocalizeException(Exception ex)
{
try
{
// Call UnlocalizedExceptionGenerator to get exception message in English
UnlocalizedExceptionGenerator ueg = new UnlocalizedExceptionGenerator(ex, System.Threading.Thread.CurrentThread.CurrentUICulture);
System.Threading.Thread thread = new System.Threading.Thread(ueg.Run)
{
CurrentCulture = CultureInfo.InvariantCulture,
CurrentUICulture = CultureInfo.InvariantCulture
};
thread.Start();
thread.Join();

return ueg.ExceptionDetails;
}
catch
{
App.Logger.LogError("Failed to translate exception");

StringBuilder sb = new StringBuilder();
sb.Append("Type=");
sb.AppendLine(ex.GetType().ToString());
sb.Append("HResult=");
sb.AppendLine("0x" + ex.HResult.ToString("X"));
sb.Append("Message=");
sb.AppendLine(ex.Message);
sb.Append("Source=");
sb.AppendLine(ex.Source);
sb.AppendLine("StackTrace:");
sb.AppendLine(ex.StackTrace);
return sb.ToString();
}
}

/// <summary>
/// Use for generate exception message in English
/// https://stackoverflow.com/questions/209133/exception-messages-in-english
/// </summary>
private class UnlocalizedExceptionGenerator
{
private Exception _ex;
private CultureInfo _origCultureInfo;

public string ExceptionDetails;

/// <summary>
/// </summary>
/// <param name="cultureInfo">Current(user’s) CultureInfo</param>
public UnlocalizedExceptionGenerator(Exception ex, CultureInfo origCultureInfo)
{
_ex = ex;
_origCultureInfo = origCultureInfo;
}

public void Run()
{
StringBuilder sb = new StringBuilder();
sb.Append("Type=");
sb.AppendLine(_ex.GetType().ToString());
sb.Append("HResult=");
sb.AppendLine("0x" + _ex.HResult.ToString("X"));
sb.Append("Message=");

// Find message in .net localize resources
string message = _ex.Message;
try
{
System.Reflection.Assembly assembly = _ex.GetType().Assembly;
ResourceManager rm = new ResourceManager(assembly.GetName().Name, assembly);
ResourceSet originalResources = rm.GetResourceSet(_origCultureInfo, true, true);
ResourceSet targetResources = rm.GetResourceSet(CultureInfo.InvariantCulture, true, true);
foreach (System.Collections.DictionaryEntry originalResource in originalResources)
{
if (originalResource.Value.ToString().Equals(_ex.Message.ToString(), StringComparison.Ordinal))
{
message = targetResources.GetString(originalResource.Key.ToString(), false);
break;
}
}
}
catch { }
sb.AppendLine(message);

sb.Append("Source=");
sb.AppendLine(_ex.Source);
sb.AppendLine("StackTrace:");
sb.AppendLine(_ex.StackTrace);

ExceptionDetails = sb.ToString();
}
}
}
}
42 changes: 35 additions & 7 deletions FrostyPlugin/Themes/Generic.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1390,26 +1390,54 @@
<DataTemplate>
<DataTemplate.Resources>
<ctrl:WindowCloseCommand x:Key="CloseCommand"/>
<local:ExceptionBoxClickCommand x:Key="exceptionBoxClickCommand" />
</DataTemplate.Resources>

<Grid Background="{StaticResource ListBackground}">
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="38"/>
</Grid.RowDefinitions>

<!-- Exception has occured text -->
<TextBlock Grid.Row="0" Text="An unhandled exception has occurred" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="{StaticResource FontColor}" FontFamily="Global User Interface" FontSize="14"/>
<!-- Header -->
<StackPanel Grid.Row="0" Margin="5, 5, 5, 0">
<TextBlock Margin="2" Text="An unhandled exception has occurred" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="{StaticResource FontColor}" FontFamily="Global User Interface" FontSize="16"/>
<TextBlock Margin="2, 0, 2, 2" Text="{Binding ExceptionMessageText, RelativeSource={RelativeSource AncestorType={x:Type local:FrostyExceptionBox}}}" TextWrapping="WrapWithOverflow" TextTrimming="None" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="{StaticResource FontColor}" FontFamily="Global User Interface"/>
</StackPanel>

<!-- Crash text -->
<TextBox Grid.Row="1" Text="{Binding ExceptionText, RelativeSource={RelativeSource AncestorType={x:Type local:FrostyExceptionBox}}}" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" IsReadOnly="True" FontFamily="Consolas" Margin="8,0,8,0" BorderThickness="1" Padding="4"/>
<TabControl Grid.Row="1" Margin="8,0,8,0">
<TabItem Header="Exception">
<!-- Crash text -->
<TextBox BorderThickness="1" Padding="4" Text="{Binding ExceptionText, RelativeSource={RelativeSource AncestorType={x:Type local:FrostyExceptionBox}}}" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" IsReadOnly="True" FontFamily="Consolas"/>
</TabItem>

<!-- Ok button -->
<TabItem Header="Log">
<!-- Log -->
<Grid>
<TextBox BorderThickness="1"
Padding="4"
FontFamily="Consolas"
IsReadOnly="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Text="{Binding LogText, RelativeSource={RelativeSource AncestorType={x:Type local:FrostyExceptionBox}}}"
/>
</Grid>
</TabItem>
</TabControl>

<Border Grid.Row="2"/>
<!-- Buttons -->
<StackPanel Grid.Row="2" Margin="8" FlowDirection="RightToLeft" Orientation="Horizontal">
<Button Content="Ok" Width="75" Command="{StaticResource CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Button Content="OK" Width="75" Command="{StaticResource CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Button x:Name="PART_IgnoreButton" Content="Ignore" ToolTip="Try to ignore exception, unknown error may occurs" Width="75" Margin="5,0,0,0" Command="{StaticResource exceptionBoxClickCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
<!--<Button x:Name="PART_ReportCrashButton" Content="Report Crash" Width="100" Margin="5,0,0,0"/>-->
</StackPanel>
<StackPanel Grid.Row="2" Margin="8" FlowDirection="LeftToRight" Orientation="Horizontal">
<Button x:Name="PART_CopyExceptionButton" Content="Copy Exception" Width="100" Margin="0,0,5,0" Command="{StaticResource exceptionBoxClickCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
<Button x:Name="PART_CopyLogButton" Content="Copy Log" Width="70" Margin="0,0,5,0" Command="{StaticResource exceptionBoxClickCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
</StackPanel>
</Grid>
</DataTemplate>
</Setter.Value>
Expand Down