unit GameDiagnosticsReport; {$mode objfpc}{$H+}{$codepage utf8} interface type TDiagnosticStatus = (dsNotRun, dsPass, dsFail); TDiagnosticStageId = ( dsiResourceExists, dsiResourceOpen, dsiCallerStreamLoad, dsiStreamState, dsiSeekReadConsistency ); TOptionalString = record HasValue: Boolean; Value: string; end; TOptionalInt64 = record HasValue: Boolean; Value: Int64; end; TOptionalBoolean = record HasValue: Boolean; Value: Boolean; end; TOptionalDiagnosticStageId = record HasValue: Boolean; Value: TDiagnosticStageId; end; TDiagnosticError = record Present: Boolean; ClassName: TOptionalString; MessageText: TOptionalString; end; TFileIODiagnosticStreamResult = record Size: TOptionalInt64; PositionAfterLoad: TOptionalInt64; ReadOffset: TOptionalInt64; RequestedReadCount: TOptionalInt64; FirstReadCount: TOptionalInt64; PositionAfterFirstRead: TOptionalInt64; SeekBackPosition: TOptionalInt64; SecondReadCount: TOptionalInt64; PositionAfterSecondRead: TOptionalInt64; RepeatEqual: TOptionalBoolean; end; TFileIODiagnosticResult = record Status: TDiagnosticStatus; FailureStage: TOptionalDiagnosticStageId; Error: TDiagnosticError; Stages: array[TDiagnosticStageId] of TDiagnosticStatus; Stream: TFileIODiagnosticStreamResult; end; TDiagnosticApplicationMetadata = record Name: string; PackageName: string; Version: string; VersionCode: Int64; BuildId: TOptionalString; end; TDiagnosticRuntimeMetadata = record Platform: string; OsVersion: TOptionalString; DeviceModel: TOptionalString; CpuArch: TOptionalString; end; TDiagnosticSelectedMapMetadata = record RelativePath: string; Url: string; end; TDiagnosticReportState = record ApplicationInfo: TDiagnosticApplicationMetadata; RuntimeInfo: TDiagnosticRuntimeMetadata; SelectedMap: TDiagnosticSelectedMapMetadata; OverallStatus: TDiagnosticStatus; FileIO: TFileIODiagnosticResult; end; function CurrentDiagnosticReport: TDiagnosticReportState; procedure ReplaceFileIODiagnosticResult(const AResult: TFileIODiagnosticResult); function DiagnosticStatusToken(const AStatus: TDiagnosticStatus): string; function DiagnosticStageIdToken(const AStage: TDiagnosticStageId): string; function DiagnosticReportJsonSnapshot: string; implementation uses fpjson, GameSelectedMap; var CurrentFileIO: TFileIODiagnosticResult; function DiagnosticStatusToken(const AStatus: TDiagnosticStatus): string; begin case AStatus of dsNotRun: Result := 'NOT_RUN'; dsPass: Result := 'PASS'; dsFail: Result := 'FAIL'; end; end; function DiagnosticStageIdToken(const AStage: TDiagnosticStageId): string; begin case AStage of dsiResourceExists: Result := 'resource_exists'; dsiResourceOpen: Result := 'resource_open'; dsiCallerStreamLoad: Result := 'caller_stream_load'; dsiStreamState: Result := 'stream_state'; dsiSeekReadConsistency: Result := 'seek_read_consistency'; end; end; function CurrentDiagnosticReport: TDiagnosticReportState; begin Result.ApplicationInfo.Name := 'kam_mobile'; Result.ApplicationInfo.PackageName := 'org.kammobile.kamremake'; Result.ApplicationInfo.Version := '0.4.0'; Result.ApplicationInfo.VersionCode := 30; Result.ApplicationInfo.BuildId.HasValue := False; Result.RuntimeInfo.Platform := 'android'; Result.RuntimeInfo.OsVersion.HasValue := False; Result.RuntimeInfo.DeviceModel.HasValue := False; Result.RuntimeInfo.CpuArch.HasValue := False; Result.SelectedMap.RelativePath := SelectedMapRelativePath; Result.SelectedMap.Url := SelectedMapUrl; Result.FileIO := CurrentFileIO; Result.OverallStatus := Result.FileIO.Status; end; procedure ReplaceFileIODiagnosticResult(const AResult: TFileIODiagnosticResult); begin CurrentFileIO := AResult; end; function OptionalStringJson(const AValue: TOptionalString): TJSONData; begin if AValue.HasValue then Result := TJSONString.Create(AValue.Value) else Result := TJSONNull.Create; end; function OptionalInt64Json(const AValue: TOptionalInt64): TJSONData; begin if AValue.HasValue then Result := TJSONIntegerNumber.Create(AValue.Value) else Result := TJSONNull.Create; end; function OptionalBooleanJson(const AValue: TOptionalBoolean): TJSONData; begin if AValue.HasValue then Result := TJSONBoolean.Create(AValue.Value) else Result := TJSONNull.Create; end; function DiagnosticReportJsonSnapshot: string; var State: TDiagnosticReportState; Root, ObjectValue, Action, Stage, Stream: TJSONObject; Actions, Stages: TJSONArray; StageId: TDiagnosticStageId; begin State := CurrentDiagnosticReport; Root := TJSONObject.Create; try Root.Add('schema', 'kam-mobile-diagnostics/1'); ObjectValue := TJSONObject.Create; ObjectValue.Add('name', State.ApplicationInfo.Name); ObjectValue.Add('package', State.ApplicationInfo.PackageName); ObjectValue.Add('version', State.ApplicationInfo.Version); ObjectValue.Add('version_code', State.ApplicationInfo.VersionCode); ObjectValue.Add('build_id', OptionalStringJson(State.ApplicationInfo.BuildId)); Root.Add('application', ObjectValue); ObjectValue := TJSONObject.Create; ObjectValue.Add('platform', State.RuntimeInfo.Platform); ObjectValue.Add('os_version', OptionalStringJson(State.RuntimeInfo.OsVersion)); ObjectValue.Add('device_model', OptionalStringJson(State.RuntimeInfo.DeviceModel)); ObjectValue.Add('cpu_arch', OptionalStringJson(State.RuntimeInfo.CpuArch)); Root.Add('runtime', ObjectValue); ObjectValue := TJSONObject.Create; ObjectValue.Add('relative_path', State.SelectedMap.RelativePath); ObjectValue.Add('url', State.SelectedMap.Url); Root.Add('selected_map', ObjectValue); Root.Add('overall_status', DiagnosticStatusToken(State.OverallStatus)); Actions := TJSONArray.Create; Action := TJSONObject.Create; Action.Add('id', 'file_io'); Action.Add('status', DiagnosticStatusToken(State.FileIO.Status)); if State.FileIO.FailureStage.HasValue then Action.Add('failure_stage', DiagnosticStageIdToken(State.FileIO.FailureStage.Value)) else Action.Add('failure_stage', TJSONNull.Create); if State.FileIO.Error.Present then begin ObjectValue := TJSONObject.Create; ObjectValue.Add('class', OptionalStringJson(State.FileIO.Error.ClassName)); ObjectValue.Add('message', OptionalStringJson(State.FileIO.Error.MessageText)); Action.Add('error', ObjectValue); end else Action.Add('error', TJSONNull.Create); Stages := TJSONArray.Create; for StageId := Low(TDiagnosticStageId) to High(TDiagnosticStageId) do begin Stage := TJSONObject.Create; Stage.Add('id', DiagnosticStageIdToken(StageId)); Stage.Add('status', DiagnosticStatusToken(State.FileIO.Stages[StageId])); Stages.Add(Stage); end; Action.Add('stages', Stages); Stream := TJSONObject.Create; Stream.Add('size', OptionalInt64Json(State.FileIO.Stream.Size)); Stream.Add('position_after_load', OptionalInt64Json(State.FileIO.Stream.PositionAfterLoad)); Stream.Add('read_offset', OptionalInt64Json(State.FileIO.Stream.ReadOffset)); Stream.Add('requested_read_count', OptionalInt64Json(State.FileIO.Stream.RequestedReadCount)); Stream.Add('first_read_count', OptionalInt64Json(State.FileIO.Stream.FirstReadCount)); Stream.Add('position_after_first_read', OptionalInt64Json(State.FileIO.Stream.PositionAfterFirstRead)); Stream.Add('seek_back_position', OptionalInt64Json(State.FileIO.Stream.SeekBackPosition)); Stream.Add('second_read_count', OptionalInt64Json(State.FileIO.Stream.SecondReadCount)); Stream.Add('position_after_second_read', OptionalInt64Json(State.FileIO.Stream.PositionAfterSecondRead)); Stream.Add('repeat_equal', OptionalBooleanJson(State.FileIO.Stream.RepeatEqual)); Action.Add('stream', Stream); Actions.Add(Action); Root.Add('actions', Actions); Result := Root.AsJSON; finally Root.Free; end; end; end.