// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
namespace Tools.CrashReporter.CrashReportCommon
{
/// Basic support for native FEngineVersion.
public class FEngineVersion
{
/// Initialization constructor.
public FEngineVersion(string StringEngineVersion)
{
// 4.10.0-0+UE4
int MinusPos = StringEngineVersion.IndexOf( '-' );
int PlusPos = StringEngineVersion.IndexOf( '+' );
if (MinusPos < 0 || PlusPos < 0 || PlusPos < MinusPos)
{
VersionNumber = string.Empty;
Branch = string.Empty;
return;
}
VersionNumber = StringEngineVersion.Substring( 0, MinusPos );
string ChangelistSring = StringEngineVersion.Substring( MinusPos + 1, PlusPos - MinusPos - 1 );
uint.TryParse( ChangelistSring, out Changelist );
Branch = StringEngineVersion.Substring( PlusPos+1 );
}
///
/// Returns the branch name corresponding to this version
///
public string GetCleanedBranch()
{
string CleanedBranch = Branch.Replace( '+', '/' );
CleanedBranch = CleanedBranch.Replace( CrashReporterConstants.P4_DEPOT_PREFIX, "" );
CleanedBranch = CleanedBranch.Substring( 0, Math.Min( 31, CleanedBranch.Length ) ); // Database limitation.
return CleanedBranch;
}
/// Version number.
public string VersionNumber;
/// Change list/ Built from CL.
public uint Changelist;
/// Branch.
public string Branch;
}
/// Helper to read useful information out of the report XML and diagnostics files
public class FReportData
{
/// Name of report for logging
public readonly string ReportName;
/// Ticks for this report data.
public readonly Int64 Ticks = DateTime.Now.Ticks;
/// Helper method, display this report data as a report name. Debugging purpose.
public override string ToString()
{
return ReportName;
}
/// Unique name of this report, can be used as name of file.
public string GetReportNameAsFilename()
{
string EngineVersion = string.Format
(
"{0}-{1}+{2}",
!string.IsNullOrEmpty( EngineBuildVersion ) ? EngineBuildVersion : "NO_BUILD_VERSION",
!string.IsNullOrEmpty( BuiltFromCL ) ? BuiltFromCL : "NO_BUILT_FROM_CL",
!string.IsNullOrEmpty( BranchName ) ? BranchName.Replace( '/', '+' ) : "NO_BRANCH"
);
string Directory = string.Format( "{0}_{1}__{2}", EngineVersion, !string.IsNullOrEmpty( GameName ) ? GameName : "NO_GAMENAME", Ticks );
return Directory;
}
/// Returns engine version as a string ie.: 4.7.0-2448202+++depot+UE4-Releases+4.7
public string GetEngineVersion()
{
string EngineVersion = string.Format
(
"{0}-{1}+{2}",
!string.IsNullOrEmpty( EngineBuildVersion ) ? EngineBuildVersion : "",
!string.IsNullOrEmpty( BuiltFromCL ) ? BuiltFromCL : "",
!string.IsNullOrEmpty( BranchNameWithDepot ) ? BranchNameWithDepot.Replace( '/', '+' ) : ""
);
// An invalid version, so return an empty string.
if( EngineVersion == "-+" )
{
return "";
}
return EngineVersion;
}
///
/// Read the callstack from the diagnostics file
/// Absolute path of the diagnostics file.
/// The callstack as an array of entries
///
static public string[] GetCallStack(string DiagnosticsPath)
{
return ExtractSection(DiagnosticsPath, "", "");
}
///
/// Read the source context from the diagnostics file
/// Absolute path of the diagnostics file.
/// Source code around the error point as an array of lines
///
static public string[] GetSourceContext(string DiagnosticsPath)
{
return ExtractSection(DiagnosticsPath, "", "");
}
/// Accessor for the game name from the report metadata
public string GameName
{
get
{
try
{
return Metadata.ProblemSignatures.Parameter0;
}
catch( System.Exception )
{
return "";
}
}
}
/// Accessor for the build version string from the report metadata
public string EngineBuildVersion
{
get
{
try
{
string FourParms = Metadata.ProblemSignatures.Parameter1;
var Subs = FourParms.Split( ".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries );
if( Subs.Length >= 3 )
{
return string.Format( "{0}.{1}.{2}", Subs[0], Subs[1], Subs[2] );
}
return "";
}
catch( System.Exception )
{
return "";
}
}
}
/// Accessor for the language identifier from the report metadata
public string Language
{
get
{
try
{
return Metadata.DynamicSignatures.Parameter2;
}
catch( System.Exception )
{
return "";
}
}
}
/// Accessor for the command line from the report metadata
public string CommandLine
{
get
{
string Result = "";
try
{
if( Metadata.ProblemSignatures != null && Metadata.ProblemSignatures.Parameter8 != null )
{
string[] Components = Metadata.ProblemSignatures.Parameter8.Split( new[] { '!' } );
if( Components.Length > 0 )
{
Result = Components[1];
}
}
}
finally
{}
return Result;
}
}
/// Accessor for the system language from the report metadata
public string SystemLanguage
{
get
{
try
{
return Metadata.OSVersionInformation.LCID;
}
catch( System.Exception )
{
return "";
}
}
}
/// Extract the branch name from the report metadata
public string BranchName
{
get
{
string BranchName = ExtractFromBaseDirString(0).Replace('+', '/');
if (BranchName.StartsWith(CrashReporterConstants.P4_DEPOT_PREFIX))
{
BranchName = BranchName.Substring( CrashReporterConstants.P4_DEPOT_PREFIX.Length );
}
BranchName = BranchName.Substring( 0, Math.Min( 31, BranchName.Length ) ); // Database limitation.
return BranchName;
}
}
/// Extract the branch name from the report metadata
public string BranchNameWithDepot
{
get
{
string BranchName = ExtractFromBaseDirString( 0 ).Replace( '+', '/' );
return BranchName;
}
}
/// Extract the base directory from the report metadata
public string BaseDir
{
get
{
return ExtractFromBaseDirString(1);
}
}
/// Extract the platform name from the report metadata
public string Platform
{
get
{
// Extract the platform name encoded in parameter9 and add the Windows product info, e.g. Windows 7 Professional
string PlatformName = "";
string BranchPath = ExtractFromBaseDirString(1);
if (BranchPath != "")
{
string[] FolderComponents = BranchPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
if (FolderComponents.Length != 0)
{
PlatformName = FolderComponents[FolderComponents.Length - 1];
}
}
if (!string.IsNullOrEmpty( Metadata.OSVersionInformation.Product ) && !string.IsNullOrEmpty( Metadata.OSVersionInformation.Architecture ))
{
PlatformName = string.Format( "{0} [{2} {1}]", PlatformName, Metadata.OSVersionInformation.Architecture == "X64" ? "64b" : "32b", Metadata.OSVersionInformation.Product );
}
return PlatformName;
}
}
/// This should be ErrorMessage=, but leave it as the same for backward compatibility.
public const string ErrorMessageMarker = "AssertLog=\"";
const string ExceptionMarker = "Exception was \"";
const string MachineIdMarker = "MachineId:";
const string LoginIdMarker = "LoginId:";
const string EpicAccountIdMarker = "EpicAccountId:";
const string UserNameMarker = "Name:";
///
/// The error message, can be assertion message, ensure message or message from the fatal error.
/// @see GErrorMessage.
///
public string[] ErrorMessage
{
get
{
var EmptyArray = new string[] { };
try
{
// This may fail if we encounter a very long error message.
int StartIndex = Metadata.ProblemSignatures.Parameter8.IndexOf( ErrorMessageMarker );
if( StartIndex > 0 )
{
string ErrorMessageFlat = Metadata.ProblemSignatures.Parameter8.Substring( StartIndex );
ErrorMessageFlat = ErrorMessageFlat.Replace( ErrorMessageMarker, "" ).Replace( "\"", "" );
ErrorMessageFlat = ErrorMessageFlat.Substring( 0, Math.Min( 511, ErrorMessageFlat.Length ) ); // Database limitation.
EmptyArray = ErrorMessageFlat.Split( new[] { '#' }, StringSplitOptions.RemoveEmptyEntries );
}
return EmptyArray;
}
catch( System.Exception )
{
return EmptyArray;
}
}
}
///
/// Exception description, used if the ErrorMessage is empty
///
static public string[] GetExceptionDescription(string DiagnosticsPath)
{
var ResultArray = new string[] { "" };
try
{
string[] Lines = File.ReadAllLines( DiagnosticsPath );
foreach( string Line in Lines )
{
if( Line.Contains( ExceptionMarker ) )
{
int StartIndex = Line.IndexOf( ExceptionMarker );
int EndIndex = Line.LastIndexOf( "\"" );
ResultArray = new string[] { Line.Substring( StartIndex, EndIndex - StartIndex ).Replace( ExceptionMarker, "" ) };
break;
}
}
return ResultArray;
}
catch( System.Exception )
{
return ResultArray;
}
}
/// Extract the engine mode from the report metadata
public string EngineMode
{
get
{
return ExtractFromBaseDirString(2);
}
}
/// Extract the CL# from the report metadata
public string BuiltFromCL
{
get
{
return ExtractFromBaseDirString(3);
}
}
/// User-provided description from the report
public string[] UserDescription
{
get
{
var EmptyArray = new string[] { };
if( Metadata.DynamicSignatures != null && Metadata.DynamicSignatures.Parameter3 != null )
{
// Description in the DB has only 512 characters.
string ExtractedDescription = Metadata.DynamicSignatures.Parameter3;
return ExtractedDescription.Substring( 0, Math.Min( 480, ExtractedDescription.Length ) ).Split( new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
}
return EmptyArray;
}
}
/// The unique ID used to identify the machine the crash occurred on.
public string MachineId
{
get
{
return ExtractMarkedString( MachineIdMarker );
}
}
/// The Login ID used to identify the user login the crash occurred on.
public string LoginId
{
get
{
return ExtractMarkedString(LoginIdMarker);
}
}
/// The Epic account ID for the user who last used the Launcher.
public string EpicAccountId
{
get
{
return ExtractMarkedString( EpicAccountIdMarker );
}
}
/// The name of the user that caused this crash.
public string UserName
{
get
{
return ExtractMarkedString( UserNameMarker );
}
}
/// Whether the user allowed us to be contacted.
public bool AllowToBeContacted
{
get
{
return Metadata.DynamicSignatures != null ? Metadata.DynamicSignatures.bAllowToBeContacted : false;
}
}
/// Game-specific deployment string e.g. "DevPlaytest", "Live"
public string DeploymentName
{
get
{
return Metadata.DynamicSignatures.DeploymentName;
}
}
/// Is the report an non-fatal error?
public bool IsEnsure
{
get
{
return Metadata.DynamicSignatures.IsEnsure;
}
}
/// Is the build version string (cannot be parsed for details - set unique per-project and build)
public string BuildVersion
{
get
{
return Metadata.DynamicSignatures.BuildVersion;
}
}
/// Is the report a crash from a deliberate check like an assert?
public bool IsAssert
{
get
{
return Metadata.DynamicSignatures.IsAssert;
}
}
/// Crash type string e.g. "Crash, "Assert", "Ensure"
public string CrashType
{
get
{
return Metadata.DynamicSignatures.CrashType;
}
}
/// Extracts a string marked with the specified string value.
private string ExtractMarkedString( string NameMarker )
{
if( Metadata.DynamicSignatures != null && Metadata.DynamicSignatures.Parameter4 != null )
{
string[] Components = Metadata.DynamicSignatures.Parameter4.Split( new[] { '!' }, StringSplitOptions.RemoveEmptyEntries );
foreach( string Component in Components )
{
if( Component.StartsWith( NameMarker ) )
{
string Result = Component.Replace( NameMarker, "" );
return Result;
}
}
}
return "";
}
///
/// Constructor: store the metadata and extract the callstack from diagnostics file
///
/// Report meta-data object, parsed from XML
/// Name of report for logging
public FReportData( WERReportMetadata Data, string InReportNamePath )
{
Metadata = Data;
ReportName = InReportNamePath;
}
///
/// Split the combined base directory string into individual components
/// Index of component to retrieve
///
string ExtractFromBaseDirString(int Index)
{
try
{
string[] Components = Metadata.ProblemSignatures.Parameter9.Split( new[] { '!' } );
return Components.Length > Index ? Components[Index] : "";
}
catch( System.Exception )
{
return "";
}
}
///
/// Extract a delimited section from the diagnostics file.
///
/// File name of the diagnostics file to interrogate.
/// A string key to mark the start of the selection.
/// A string key to mark the end of the selection.
/// A string array of the extracted section.
static string[] ExtractSection( string Filepath, string StartKey, string EndKey )
{
List Section = new List();
string[] Lines = File.ReadAllLines( Filepath );
bool bCollecting = false;
foreach( string Line in Lines )
{
if( Line.StartsWith( StartKey ) )
{
bCollecting = true;
}
else if( Line.StartsWith( EndKey ) )
{
bCollecting = false;
break;
}
else if( bCollecting )
{
Section.Add( Line.Trim( Environment.NewLine.ToCharArray() ) );
}
}
return Section.ToArray();
}
/// Metadata read from xml file
WERReportMetadata Metadata;
}
}