unit AssettoCorsa.SharedMemory;

interface
type
  AC_STATUS = Integer;
  TACStatus = AC_STATUS;


const
  { AC_STATUS }
  AC_OFF = 0;
  AC_REPLAY = 1;
  AC_LIVE = 2;
  AC_PAUSE = 3;


type
  AC_SESSION_TYPE = Integer;
  TACSessionType = AC_SESSION_TYPE;

const
  { AC_SESSION_TYPE }
  AC_UNKNOWN = -1;
  AC_PRACTICE = 0;
  AC_QUALIFY = 1;
  AC_RACE = 2;
  AC_HOTLAP = 3;
  AC_TIME_ATTACK = 4;
  AC_DRIFT = 5;
  AC_DRAG = 6;


type
  AC_FLAG_TYPE = Integer;
  TACFlagType = AC_FLAG_TYPE;

const
  { AC_FLAG_TYPE }
  AC_NO_FLAG = 0;
  AC_BLUE_FLAG = 1;
  AC_YELLOW_FLAG = 2;
  AC_BLACK_FLAG = 3;
  AC_WHITE_FLAG = 4;
  AC_CHECKERED_FLAG = 5;
  AC_PENALTY_FLAG = 6;


type
  TACSMPhysics = packed record
    packetId: Integer;
    gas: Single;
    brake: Single;
    fuel: Single;
    gear: Integer;
    rpms: Integer;
    steerAngle: Single;
    speedKmh: Single;
    velocity: array[0..2] of Single;
    accG: array[0..2] of Single;
    wheelSlip: array[0..3] of Single;
    wheelLoad: array[0..3] of Single;
    wheelsPressure: array[0..3] of Single;
    wheelAngularSpeed: array[0..3] of Single;
    tyreWear: array[0..3] of Single;
    tyreDirtyLevel: array[0..3] of Single;
    tyreCoreTemperature: array[0..3] of Single;
    camberRAD: array[0..3] of Single;
    suspensionTravel: array[0..3] of Single;
    drs: Single;
    tc: Single;
    heading: Single;
    pitch: Single;
    roll: Single;
    cgHeight: Single;
    carDamage: array[0..4] of Single;
    umberOfTyresOut: Integer;
    pitLimiterOn: Integer;
    abs: Single;
    kersCharge: Single;
    kersInput: Single;
    autoShifterOn: Integer;
    rideHeigh: Single;
    turboBoost: Single;
    ballast: Single;
    airDensity: Single;
    airTemp: Single;
    roadTemp: Single;
    localAngularVelocity: array[0..2] of Single;
    finalFF: Single;
    performanceMeter: Single;

    engineBrake: Integer;
    ersRecoveryLevel: Integer;
    ersPowerLevel: Integer;
    ersHeatCharging: Integer;
    ersIsCharging: Integer;
    kersCurrentKJ: Single;

    drsAvailable: Integer;
    drsEnabled: Integer;

    brakeTemp: array[0..3] of Single;
    clutch: Single;

    tyreTempI: array[0..3] of Single;
    tyreTempM: array[0..3] of Single;
    tyreTempO: array[0..3] of Single;

    isAIControlled: Integer;

    tyreContactPoint: array[0..3, 0..2] of Single;
    tyreContactNormal: array[0..3, 0..2] of Single;
    tyreContactHeading: array[0..3, 0..2] of Single;

    brakeBias: Single;
  end;
  PACSMPhysics = ^TACSMPhysics;


  TACSMGraphic = packed record
    packetId: Integer;
    status: TACStatus;
    session: TACSessionType;
    currentTime: array[0..14] of WideChar;
    lastTime: array[0..14] of WideChar;
    bestTime: array[0..14] of WideChar;
    split: array[0..14] of WideChar;
    completedLaps: Integer;
    position: Integer;
    iCurrentTime: Integer;
    iLastTime: Integer;
    iBestTime: Integer;
    sessionTimeLeft: Single;
    distanceTraveled: Single;
    isInPit: Integer;
    currentSectorIndex: Integer;
    lastSectorTime: Integer;
    numberOfLaps: Integer;
    tyreCompound: array[0..32] of WideChar;

    replayTimeMultiplier: Single;
    normalizedCarPosition: Single;
    carCoordinates: array[0..2] of Single;
    penaltyTime: Single;
    flag: TACFlagType;
    idealLineOn: Integer;
    isInPitLane: Integer;

    surfaceGrip: Single;
  end;
  PACSMGraphic = ^TACSMGraphic;


  TACSMStatic = packed record
    smVersion: array[0..14] of WideChar;
    acVersion: array[0..14] of WideChar;

    // Session static info
    numberOfSessions: Integer;
    numCars: Integer;
    carModel: array[0..32] of WideChar;
    track: array[0..32] of WideChar;
    playerName: array[0..32] of WideChar;
    playerSurname: array[0..32] of WideChar;
    playerNick: array[0..32] of WideChar;
    sectorCount: Integer;

    // Car static info
    maxTorque: Single;
    maxPower: Single;
    maxRpm: Integer;
    maxFuel: Single;
    suspensionMaxTravel: array[0..3] of Single;
    tyreRadius: array[0..3] of Single;
    maxTurboBoost: Single;

    deprecated_1: Single;
    deprecated_2: Single;

    penaltiesEnabled: Integer;

    aidFuelRate: Single;
    aidTireRate: Single;
    aidMechanicalDamage: Single;
    aidAllowTyreBlankets: Integer;
    aidStability: Single;
    aidAutoClutch: Integer;
    aidAutoBlip: Integer;

    hasDRS: Integer;
    hasERS: Integer;
    hasKERS: Integer;
    kersMaxJ: Single;
    engineBrakeSettingsCount: Integer;
    ersPowerControllerCount: Integer;
    trackSPlineLength: Single;
    trackConfiguration: array[0..32] of WideChar;
    ersMaxJ: Single;
  end;
  PACSMStatic = ^TACSMStatic;


const
  ACSM_PHYSICS = 'Local\acpmf_physics';
  ACSM_GRAPHICS = 'Local\acpmf_graphics';
  ACSM_STATIC = 'Local\acpmf_static';


type
  IAssettoCorsaSharedMemory = interface
    ['{D7C0E678-CB11-4C34-81EB-4AAD5737C44F}']
    function GetPhysics(out APhysics: TACSMPhysics): Boolean;
    function GetGraphic(out AGraphic: TACSMGraphic): Boolean;
    function GetStatic(out AStatic: TACSMStatic): Boolean;
  end;


  function GetAssettoCorsaSharedMemory(): IAssettoCorsaSharedMemory;


implementation
uses
  WinApi.Windows, System.SysUtils, System.Classes;


type
  TACSMMapping = record
    Handle: THandle;
    Buffer: Pointer;
  end;


  TAssettoCorsaSharedMemory = class(TInterfacedObject, IAssettoCorsaSharedMemory)
  private
    FPhysicsMapping: TACSMMapping;
    FGraphicMapping: TACSMMapping;
    FStaticMapping: TACSMMapping;
  protected
    function OpenMapping(const AName: string; var AMapping: TACSMMapping; ASize: Integer): Boolean;
    procedure CloseMapping(var AMapping: TACSMMapping);
  public
    destructor Destroy; override;

    { IACSM }
    function GetPhysics(out APhysics: TACSMPhysics): Boolean;
    function GetGraphic(out AGraphic: TACSMGraphic): Boolean;
    function GetStatic(out AStatic: TACSMStatic): Boolean;
  end;



function GetAssettoCorsaSharedMemory(): IAssettoCorsaSharedMemory;
begin
  Result := TAssettoCorsaSharedMemory.Create;
end;


{ TACSM }
destructor TAssettoCorsaSharedMemory.Destroy;
begin
  CloseMapping(FPhysicsMapping);
  CloseMapping(FGraphicMapping);
  CloseMapping(FStaticMapping);

  inherited;
end;


function TAssettoCorsaSharedMemory.GetPhysics(out APhysics: TACSMPhysics): Boolean;
begin
  Result := OpenMapping(ACSM_PHYSICS, FPhysicsMapping, SizeOf(TACSMPhysics));
  if Result then
    APhysics := PACSMPhysics(FPhysicsMapping.Buffer)^;
end;


function TAssettoCorsaSharedMemory.GetGraphic(out AGraphic: TACSMGraphic): Boolean;
begin
  Result := OpenMapping(ACSM_GRAPHICS, FGraphicMapping, SizeOf(TACSMGraphic));
  if Result then
    AGraphic := PACSMGraphic(FGraphicMapping.Buffer)^;
end;


function TAssettoCorsaSharedMemory.GetStatic(out AStatic: TACSMStatic): Boolean;
begin
  Result := OpenMapping(ACSM_STATIC, FStaticMapping, SizeOf(TACSMStatic));
  if Result then
    AStatic := PACSMStatic(FStaticMapping.Buffer)^;
end;


function TAssettoCorsaSharedMemory.OpenMapping(const AName: string; var AMapping: TACSMMapping; ASize: Integer): Boolean;
var
  handle: THandle;
  buffer: Pointer;

begin
  if AMapping.Handle <> 0 then
  begin
    Result := True;
    exit;
  end;

  Result := False;
  handle := OpenFileMapping(FILE_MAP_READ, False, PChar(AName));
  if handle = 0 then
    Exit;

  buffer := MapViewOfFile(handle, FILE_MAP_READ, 0, 0, ASize);
  if Assigned(buffer) then
  begin
    AMapping.Handle := handle;
    AMapping.Buffer := buffer;
    Result := True;
  end else
  begin
    CloseHandle(handle);
    RaiseLastOSError;
  end;
end;


procedure TAssettoCorsaSharedMemory.CloseMapping(var AMapping: TACSMMapping);
begin
  if AMapping.Handle <> 0 then
  begin
    UnmapViewOfFile(AMapping.Buffer);
    CloseHandle(AMapping.Handle);
  end;
end;

end.
