265 lines
7.7 KiB
ObjectPascal
265 lines
7.7 KiB
ObjectPascal
unit GameDiagnosticsFileIO;
|
|||
|
|
|
||
|
|
{$mode objfpc}{$H+}
|
||
|
|
|
||
|
|
interface
|
||
|
|
|
||
|
|
procedure RunFileIODiagnostic;
|
||
|
|
|
||
|
|
implementation
|
||
|
|
|
||
|
|
uses
|
||
|
|
Classes, SysUtils, GameSelectedMap, GameDiagnosticsReport, KaMResourceInput,
|
||
|
|
KM_CommonClasses;
|
||
|
|
|
||
|
|
procedure InitializeResult(out AResult: TFileIODiagnosticResult);
|
||
|
|
var
|
||
|
|
Stage: TDiagnosticStageId;
|
||
|
|
begin
|
||
|
|
FillChar(AResult, SizeOf(AResult), 0);
|
||
|
|
AResult.Status := dsNotRun;
|
||
|
|
for Stage := Low(TDiagnosticStageId) to High(TDiagnosticStageId) do
|
||
|
|
AResult.Stages[Stage] := dsNotRun;
|
||
|
|
end;
|
||
|
|
|
||
|
|
procedure FailStage(var AResult: TFileIODiagnosticResult;
|
||
|
|
const AStage: TDiagnosticStageId; const AMessage: string;
|
||
|
|
const AException: Exception = nil);
|
||
|
|
begin
|
||
|
|
AResult.Stages[AStage] := dsFail;
|
||
|
|
AResult.Status := dsFail;
|
||
|
|
AResult.FailureStage.HasValue := True;
|
||
|
|
AResult.FailureStage.Value := AStage;
|
||
|
|
AResult.Error.Present := True;
|
||
|
|
AResult.Error.MessageText.HasValue := True;
|
||
|
|
AResult.Error.MessageText.Value := AMessage;
|
||
|
|
if AException <> nil then
|
||
|
|
begin
|
||
|
|
AResult.Error.ClassName.HasValue := True;
|
||
|
|
AResult.Error.ClassName.Value := AException.ClassName;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
procedure FailException(var AResult: TFileIODiagnosticResult;
|
||
|
|
const AStage: TDiagnosticStageId; const AException: Exception);
|
||
|
|
begin
|
||
|
|
FailStage(AResult, AStage, AException.Message, AException);
|
||
|
|
end;
|
||
|
|
|
||
|
|
function RunResourceExists(const AMapUrl: string;
|
||
|
|
var AResult: TFileIODiagnosticResult): Boolean;
|
||
|
|
begin
|
||
|
|
try
|
||
|
|
Result := KaMResourceExists(AMapUrl);
|
||
|
|
if Result then
|
||
|
|
AResult.Stages[dsiResourceExists] := dsPass
|
||
|
|
else
|
||
|
|
FailStage(AResult, dsiResourceExists,
|
||
|
|
'Selected resource is not present');
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
begin
|
||
|
|
FailException(AResult, dsiResourceExists, E);
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
function RunResourceOpen(const AMapUrl: string;
|
||
|
|
var AResult: TFileIODiagnosticResult): Boolean;
|
||
|
|
var
|
||
|
|
Source: TStream;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
Source := nil;
|
||
|
|
try
|
||
|
|
try
|
||
|
|
Source := KaMOpenResourceStream(AMapUrl);
|
||
|
|
if Source = nil then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiResourceOpen, 'Resource open returned no stream');
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
Source.Free;
|
||
|
|
Source := nil;
|
||
|
|
AResult.Stages[dsiResourceOpen] := dsPass;
|
||
|
|
Result := True;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
FailException(AResult, dsiResourceOpen, E);
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
if Source <> nil then
|
||
|
|
try
|
||
|
|
Source.Free;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
if AResult.Status <> dsFail then
|
||
|
|
FailException(AResult, dsiResourceOpen, E);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
function RunCallerStreamLoad(const AMapUrl: string;
|
||
|
|
var AStream: TKMemoryStreamBinary; var AResult: TFileIODiagnosticResult): Boolean;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
try
|
||
|
|
AStream := TKMemoryStreamBinary.Create;
|
||
|
|
AStream.LoadFromFile(AMapUrl);
|
||
|
|
AResult.Stages[dsiCallerStreamLoad] := dsPass;
|
||
|
|
Result := True;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
FailException(AResult, dsiCallerStreamLoad, E);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
function RunStreamState(const AStream: TKMemoryStreamBinary;
|
||
|
|
var AResult: TFileIODiagnosticResult): Boolean;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
try
|
||
|
|
AResult.Stream.Size.Value := AStream.Size;
|
||
|
|
AResult.Stream.Size.HasValue := True;
|
||
|
|
AResult.Stream.PositionAfterLoad.Value := AStream.Position;
|
||
|
|
AResult.Stream.PositionAfterLoad.HasValue := True;
|
||
|
|
if AStream.Size <= 0 then
|
||
|
|
FailStage(AResult, dsiStreamState, 'Loaded stream is empty')
|
||
|
|
else if AStream.Position <> 0 then
|
||
|
|
FailStage(AResult, dsiStreamState, 'Loaded stream position is not zero')
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
AResult.Stages[dsiStreamState] := dsPass;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
FailException(AResult, dsiStreamState, E);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
function RunSeekReadConsistency(const AStream: TKMemoryStreamBinary;
|
||
|
|
var AResult: TFileIODiagnosticResult): Boolean;
|
||
|
|
var
|
||
|
|
Requested, FirstCount, SecondCount, ExpectedPosition, RestoredPosition: LongInt;
|
||
|
|
FirstBytes, SecondBytes: array of Byte;
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
try
|
||
|
|
try
|
||
|
|
AResult.Stream.ReadOffset.Value := 0;
|
||
|
|
AResult.Stream.ReadOffset.HasValue := True;
|
||
|
|
Requested := 32;
|
||
|
|
if AStream.Size < Requested then Requested := LongInt(AStream.Size);
|
||
|
|
AResult.Stream.RequestedReadCount.Value := Requested;
|
||
|
|
AResult.Stream.RequestedReadCount.HasValue := True;
|
||
|
|
SetLength(FirstBytes, Requested);
|
||
|
|
SetLength(SecondBytes, Requested);
|
||
|
|
|
||
|
|
FirstCount := AStream.Read(FirstBytes[0], Requested);
|
||
|
|
AResult.Stream.FirstReadCount.Value := FirstCount;
|
||
|
|
AResult.Stream.FirstReadCount.HasValue := True;
|
||
|
|
AResult.Stream.PositionAfterFirstRead.Value := AStream.Position;
|
||
|
|
AResult.Stream.PositionAfterFirstRead.HasValue := True;
|
||
|
|
ExpectedPosition := FirstCount;
|
||
|
|
if (FirstCount <> Requested) or (AStream.Position <> ExpectedPosition) then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiSeekReadConsistency, 'First read did not advance as required');
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
|
||
|
|
AResult.Stream.SeekBackPosition.Value := AStream.Seek(0, soBeginning);
|
||
|
|
AResult.Stream.SeekBackPosition.HasValue := True;
|
||
|
|
if (AResult.Stream.SeekBackPosition.Value <> 0) or (AStream.Position <> 0) then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiSeekReadConsistency, 'Seek back did not return to zero');
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
|
||
|
|
SecondCount := AStream.Read(SecondBytes[0], Requested);
|
||
|
|
AResult.Stream.SecondReadCount.Value := SecondCount;
|
||
|
|
AResult.Stream.SecondReadCount.HasValue := True;
|
||
|
|
AResult.Stream.PositionAfterSecondRead.Value := AStream.Position;
|
||
|
|
AResult.Stream.PositionAfterSecondRead.HasValue := True;
|
||
|
|
ExpectedPosition := SecondCount;
|
||
|
|
if (SecondCount <> Requested) or (AStream.Position <> ExpectedPosition) then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiSeekReadConsistency, 'Second read did not advance as required');
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
|
||
|
|
AResult.Stream.RepeatEqual.Value := True;
|
||
|
|
AResult.Stream.RepeatEqual.HasValue := True;
|
||
|
|
for I := 0 to Requested - 1 do
|
||
|
|
if FirstBytes[I] <> SecondBytes[I] then
|
||
|
|
begin
|
||
|
|
AResult.Stream.RepeatEqual.Value := False;
|
||
|
|
Break;
|
||
|
|
end;
|
||
|
|
if not AResult.Stream.RepeatEqual.Value then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiSeekReadConsistency, 'Repeated reads differ');
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
FailException(AResult, dsiSeekReadConsistency, E);
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
try
|
||
|
|
RestoredPosition := AStream.Seek(AResult.Stream.PositionAfterLoad.Value, soBeginning);
|
||
|
|
if ((RestoredPosition <> AResult.Stream.PositionAfterLoad.Value) or
|
||
|
|
(AStream.Position <> AResult.Stream.PositionAfterLoad.Value)) and
|
||
|
|
(AResult.Status <> dsFail) then
|
||
|
|
begin
|
||
|
|
FailStage(AResult, dsiSeekReadConsistency, 'Could not restore stream position');
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
if AResult.Status <> dsFail then
|
||
|
|
begin
|
||
|
|
FailException(AResult, dsiSeekReadConsistency, E);
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if AResult.Status <> dsFail then
|
||
|
|
begin
|
||
|
|
AResult.Stages[dsiSeekReadConsistency] := dsPass;
|
||
|
|
AResult.Status := dsPass;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
procedure RunFileIODiagnostic;
|
||
|
|
var
|
||
|
|
ResultData: TFileIODiagnosticResult;
|
||
|
|
MapUrl: string;
|
||
|
|
Stream: TKMemoryStreamBinary;
|
||
|
|
begin
|
||
|
|
InitializeResult(ResultData);
|
||
|
|
Stream := nil;
|
||
|
|
MapUrl := SelectedMapUrl;
|
||
|
|
try
|
||
|
|
if RunResourceExists(MapUrl, ResultData) and
|
||
|
|
RunResourceOpen(MapUrl, ResultData) and
|
||
|
|
RunCallerStreamLoad(MapUrl, Stream, ResultData) and
|
||
|
|
RunStreamState(Stream, ResultData) then
|
||
|
|
RunSeekReadConsistency(Stream, ResultData);
|
||
|
|
finally
|
||
|
|
try
|
||
|
|
Stream.Free;
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
if ResultData.Status <> dsFail then
|
||
|
|
FailException(ResultData, dsiSeekReadConsistency, E);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
ReplaceFileIODiagnosticResult(ResultData);
|
||
|
|
end;
|
||
|
|
|
||
|
|
end.
|