190 lines
5.9 KiB
ObjectPascal
190 lines
5.9 KiB
ObjectPascal
unit KM_Crashreport;
|
|
{$I KaM_Remake.inc}
|
|
interface
|
|
uses
|
|
MadExcept,
|
|
KM_GameTypes,
|
|
KM_Defaults;
|
|
|
|
type
|
|
// Intention of this class is to encapsulate the crash report generation,
|
|
// since it is already quite complex task and spans several methods,
|
|
// which are not quite related to the Game's SRP
|
|
TKMCrashReport = class
|
|
private
|
|
fExceptIntf: IMEException;
|
|
fZipFile: UnicodeString;
|
|
|
|
fAttachedFilesStr: UnicodeString;
|
|
procedure AttachFile(const aFile: UnicodeString);
|
|
procedure AttachEverything;
|
|
procedure AttachSaveFiles(const aFile: UnicodeString; aAttachRNG: Boolean = True);
|
|
procedure AttachLoadedFiles(aAttachRNG: Boolean);
|
|
public
|
|
constructor Create(const aExceptIntf: IMEException; const aZipFile: UnicodeString);
|
|
class procedure Generate(const aExceptIntf: IMEException; const aZipFile: UnicodeString);
|
|
end;
|
|
|
|
implementation
|
|
uses
|
|
SysUtils, Math,
|
|
KM_CommonUtils, KM_InterfaceTypes, KM_Log, KM_Game;
|
|
|
|
|
|
constructor TKMCrashReport.Create(const aExceptIntf: IMEException; const aZipFile: UnicodeString);
|
|
begin
|
|
inherited Create;
|
|
|
|
fExceptIntf := aExceptIntf;
|
|
fZipFile := aZipFile;
|
|
end;
|
|
|
|
|
|
procedure TKMCrashReport.AttachFile(const aFile: UnicodeString);
|
|
begin
|
|
if (aFile <> '') and FileExists(aFile) then
|
|
begin
|
|
if Pos(aFile, fAttachedFilesStr) = 0 then
|
|
begin
|
|
fAttachedFilesStr := fAttachedFilesStr + aFile + '; ';
|
|
fExceptIntf.AdditionalAttachments.Add(aFile, '', fZipFile);
|
|
gLog.AddTime('Attached file: ' + aFile);
|
|
end
|
|
else
|
|
gLog.AddTime('File already attached: ' + aFile);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TKMCrashReport.AttachSaveFiles(const aFile: UnicodeString; aAttachRNG: Boolean = True);
|
|
begin
|
|
AttachFile(ChangeFileExt(aFile, EXT_SAVE_MAIN_DOT));
|
|
AttachFile(ChangeFileExt(aFile, EXT_SAVE_BASE_DOT));
|
|
AttachFile(ChangeFileExt(aFile, EXT_SAVE_REPLAY_DOT));
|
|
AttachFile(ChangeFileExt(aFile, EXT_SAVE_MP_LOCAL_DOT));
|
|
if aAttachRNG then
|
|
AttachFile(ChangeFileExt(aFile, EXT_SAVE_RNG_LOG_DOT));
|
|
end;
|
|
|
|
|
|
procedure TKMCrashReport.AttachLoadedFiles(aAttachRNG: Boolean);
|
|
begin
|
|
if gGame.SaveFile = '' then Exit;
|
|
|
|
gLog.AddTime('Attaching game loaded file: ' + ExeDir + gGame.SaveFile);
|
|
AttachSaveFiles(ExeDir + gGame.SaveFile, aAttachRNG);
|
|
end;
|
|
|
|
|
|
procedure TKMCrashReport.AttachEverything;
|
|
var
|
|
index: Byte;
|
|
missionFile, path: UnicodeString;
|
|
searchRec: TSearchRec;
|
|
indexesSet: set of Byte;
|
|
begin
|
|
gLog.AddTime('Creating crash report...');
|
|
|
|
// Attempt to save the game, but if the state is too messed up it might fail
|
|
gGame.SaveWorkerThreadHolder.Worker.fSynchronousExceptionMode := True; //Do saving synchronously in main thread
|
|
try
|
|
try
|
|
if (gGame.Params.Mode in [gmSingle, gmCampaign, gmMulti, gmMultiSpectate])
|
|
and not (gGame.GamePlayInterface.UIMode = umReplay) then //In case game mode was altered or loaded with logical error
|
|
begin
|
|
gGame.Save(CRASHREPORT_SAVE_NAME, UTCNow, gGame.SaveWorkerThreadHolder.Worker);
|
|
gGame.SaveWorkerThreadHolder.Worker.WaitForAllWorkToComplete; //Wait till save is made
|
|
AttachSaveFiles(gGame.SaveName(CRASHREPORT_SAVE_NAME, EXT_SAVE_MAIN, gGame.Params.IsMultiPlayerOrSpec));
|
|
end;
|
|
except
|
|
on E : Exception do
|
|
gLog.AddTime('Exception while trying to save game for crash report: ' + E.ClassName + ': ' + E.Message
|
|
{$IFDEF WDC}+ sLineBreak + E.StackTrace{$ENDIF});
|
|
end;
|
|
finally
|
|
gGame.SaveWorkerThreadHolder.Worker.fSynchronousExceptionMode := False;
|
|
end;
|
|
|
|
missionFile := gGame.Params.MissionFileRel;
|
|
path := ExtractFilePath(ExeDir + missionFile);
|
|
|
|
// Try to attach the dat+map
|
|
AttachFile(ExeDir + missionFile);
|
|
AttachFile(ExeDir + ChangeFileExt(missionFile, '.map'));
|
|
AttachFile(ExeDir + ChangeFileExt(missionFile, '.txt'));
|
|
|
|
// Try to add main script file and all other scripts, because they could be included
|
|
if FileExists(ExeDir + ChangeFileExt(missionFile, '.script')) then
|
|
begin
|
|
FindFirst(path + '*.script', faAnyFile - faDirectory, searchRec);
|
|
try
|
|
repeat
|
|
if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
|
|
AttachFile(path + searchRec.Name);
|
|
until (FindNext(searchRec) <> 0);
|
|
finally
|
|
FindClose(searchRec);
|
|
end;
|
|
end;
|
|
|
|
// Try to add libx files
|
|
FindFirst(path + '*.libx', faAnyFile - faDirectory, searchRec);
|
|
try
|
|
repeat
|
|
if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
|
|
AttachFile(path + searchRec.Name);
|
|
until (FindNext(searchRec) <> 0);
|
|
finally
|
|
FindClose(searchRec);
|
|
end;
|
|
|
|
if gGame.Params.IsReplay
|
|
or ((gGame.GamePlayInterface <> nil) and (gGame.GamePlayInterface.UIMode = umReplay)) then //In case game mode was altered or loaded with logical error
|
|
// For replays attach only replay save files
|
|
AttachLoadedFiles(False)
|
|
else
|
|
if not gGame.Params.IsMapEditor then // no need autosaves for MapEd error...
|
|
begin
|
|
// For other game modes attach last saves
|
|
|
|
// We want to attach up to 3 saves, lets determine what are best indexes of last saves to attach
|
|
indexesSet := [];
|
|
// 0 is the oldest save
|
|
case gGame.LastSaves.Count of
|
|
0: ;
|
|
1,2,3: indexesSet := [0,1,2];
|
|
4: indexesSet := [0,2,3];
|
|
else
|
|
indexesSet := [0, gGame.LastSaves.Count div 2, gGame.LastSaves.Count - 1];
|
|
end;
|
|
|
|
for index in indexesSet do
|
|
begin
|
|
if index >= gGame.LastSaves.Count then Break;
|
|
|
|
AttachSaveFiles(gGame.SaveName(gGame.LastSaves[index], EXT_SAVE_MAIN, gGame.Params.IsMultiPlayerOrSpec));
|
|
end;
|
|
|
|
// It could be usefull to attach loaded file, to check what was wrong there
|
|
AttachLoadedFiles(True); // Also attach RNG file
|
|
end;
|
|
|
|
gLog.AddTime('Crash report created');
|
|
end;
|
|
|
|
|
|
class procedure TKMCrashReport.Generate(const aExceptIntf: IMEException; const aZipFile: UnicodeString);
|
|
var
|
|
cr: TKMCrashReport;
|
|
begin
|
|
cr := TKMCrashReport.Create(aExceptIntf, aZipFile);
|
|
try
|
|
cr.AttachEverything;
|
|
finally
|
|
cr.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
end.
|