unit ucdimage;

interface

uses
  utim;

const
  cSectorHeaderSize = 12;
  cSectorAddressSize = 3;
  cSectorModeSize = 1;
  cSectorSubHeaderSize = 8;
  cSectorInfoSize = cSectorHeaderSize + cSectorAddressSize + cSectorModeSize +
    cSectorSubHeaderSize;
  cSectorDataSize = 2048;
  cSectorECCSize = 4;
  cSectorEDCSize = 276;
  cSectorECCEDCSize = cSectorECCSize + cSectorEDCSize;
  cSectorSize = cSectorInfoSize + cSectorDataSize + cSectorECCEDCSize;

type
  TCDSector = packed record
    dwHeader: array [0 .. cSectorHeaderSize - 1] of byte;
    dwAddress: array [0 .. cSectorAddressSize - 1] of byte;
    bMode: byte;
    dwSubHeader: array [0 .. cSectorSubHeaderSize - 1] of byte;
    dwData: array [0 .. cSectorDataSize - 1] of byte;
    dwECC: array [0 .. cSectorECCSize - 1] of byte;
    dwEDC: array [0 .. cSectorEDCSize - 1] of byte;
  end;

  PCDSector = ^TCDSector;

function GetImageScan(const FileName: string): Boolean;
function ReplaceTimInFile(const FileName, TimToInsert: string; InsertTo: Integer;
  ImageScan: Boolean): Boolean;
procedure ReplaceTimInFileFromMemory(const FileName: string; TIM: PTIM;
  InsertTo: Integer; ImageScan: Boolean);

implementation

uses
  ucommon, ecc, edc, classes, LazUTF8, LazFileUtils, sysutils;

function bin2bcd(P: Integer): byte;
begin
  Result := ((P div 10) shl 4) or (P mod 10);
end;

procedure BuildAdress(LBA: Integer; var Dest);
var
  P: PByte;
begin
  Inc(LBA, 75 * 2); // 2 seconds
  P := @Dest;
  P^ := bin2bcd(LBA div (60 * 75));
  Inc(P);
  P^ := bin2bcd((LBA div 75) mod 60);
  Inc(P);
  P^ := bin2bcd(LBA mod 75);
  Inc(P);
  P^ := 2;
end;

function GetImageScan(const FileName: string): Boolean;
const
  cSectorHeader: array [0 .. cSectorHeaderSize - 1] of byte = ($00, $FF, $FF,
    $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $00);
var
  Sz: cardinal;
  pFile: PBytesArray;
  tmp: TFileStream;
begin
  Result := False;
  Sz := FileSizeUTF8(FileName);

  if (Sz > cMaxFileSize) or (Sz = 0) then Exit;

  pFile := GetMemory(cSectorHeaderSize);

  tmp := TFileStream.Create(Utf8ToSys(FileName), fmOpenRead or fmShareDenyWrite);
  tmp.Read(pFile^[0], cSectorHeaderSize);
  Result := ((Sz mod cSectorSize) = 0) and (CompareMem(@cSectorHeader, pFile, cSectorHeaderSize));

  tmp.free;
  FreeMemory(pFile);
end;

procedure ReplaceTimInFileFromMemory(const FileName: string; TIM: PTIM;
  InsertTo: Integer; ImageScan: Boolean);
type
  TSecAddrAndMode = array [0 .. cSectorAddressSize + cSectorModeSize - 1] of byte;
var
  sImageStream: TFileStream;
  TimOffsetInSector, FirstPartSize, LastPartSize: Integer;
  TimSectorNumber, TimStartSectorPos: Integer;
  Sector: TCDSector;
  ecc: DWORD;
  P, TIM_FULL_SECTORS: Integer;
  SecAddrAndMode: TSecAddrAndMode;
begin
  sImageStream := TFileStream.Create(UTF8ToSys(FileName), fmOpenReadWrite or fmShareDenyWrite);

  if not ImageScan then
  begin
    sImageStream.Seek(InsertTo, soBeginning);
    sImageStream.Write(TIM^.DATA^[0], TIM^.dwSIZE);
  end
  else
  begin
    P := 0;

    TimSectorNumber := InsertTo div cSectorSize + 1;
    TimOffsetInSector := InsertTo mod cSectorSize - cSectorInfoSize;
    TimStartSectorPos := (TimSectorNumber - 1) * cSectorSize;
    FirstPartSize := cSectorDataSize - TimOffsetInSector;

    if TIM^.dwSIZE < FirstPartSize then
      FirstPartSize := TIM^.dwSIZE;

    sImageStream.Seek(TimStartSectorPos, soBeginning);
    FillChar(Sector, cSectorSize, 0);

    sImageStream.Read(Sector, cSectorSize);
    sImageStream.Seek(Int64(TimStartSectorPos) + Int64(cSectorHeaderSize), soBeginning);

    Move(Sector.dwAddress[0], SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);
    FillChar(Sector.dwAddress[0], cSectorAddressSize + cSectorModeSize, 0);

    Move(TIM^.DATA^[P], Sector.dwData[TimOffsetInSector], FirstPartSize);
    Inc(P, FirstPartSize);

    ecc := build_edc(@(Sector.dwSubHeader[0]), cSectorSubHeaderSize + cSectorDataSize);
    Move(ecc, Sector.dwECC, cSectorECCSize);
    encode_L2_P(@(Sector.dwAddress[0]));
    encode_L2_Q(@(Sector.dwAddress[0]));
    BuildAdress(TimSectorNumber, Sector.dwAddress[0]);

    sImageStream.Seek(TimStartSectorPos, soBeginning);
    sImageStream.Write(Sector, cSectorSize);
    sImageStream.Seek(Int64(TimStartSectorPos) + Int64(cSectorHeaderSize), soBeginning);
    sImageStream.Write(SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);

    Inc(TimStartSectorPos, cSectorSize);
    sImageStream.Seek(TimStartSectorPos, soBeginning);

    TIM_FULL_SECTORS := (TIM^.dwSIZE - P) div cSectorDataSize;

    while TIM_FULL_SECTORS > 0 do
    begin
      Inc(TimSectorNumber);

      sImageStream.Read(Sector, cSectorSize);
      Move(Sector.dwAddress[0], SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);
      FillChar(Sector.dwAddress[0], cSectorAddressSize + cSectorModeSize, 0);

      Move(TIM^.DATA^[P], Sector.dwData[0], cSectorDataSize);
      Inc(P, cSectorDataSize);

      ecc := build_edc(@(Sector.dwSubHeader[0]), cSectorSubHeaderSize + cSectorDataSize);
      Move(ecc, Sector.dwECC, cSectorECCSize);
      encode_L2_P(@(Sector.dwAddress[0]));
      encode_L2_Q(@(Sector.dwAddress[0]));
      BuildAdress(TimSectorNumber, Sector.dwAddress[0]);

      sImageStream.Seek(TimStartSectorPos, soBeginning);
      sImageStream.Write(Sector, cSectorSize);
      sImageStream.Seek(Int64(TimStartSectorPos) + Int64(cSectorHeaderSize), soBeginning);
      sImageStream.Write(SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);

      Inc(TimStartSectorPos, cSectorSize);
      sImageStream.Seek(TimStartSectorPos, soBeginning);

      Dec(TIM_FULL_SECTORS);
    end;

    Inc(TimSectorNumber);

    sImageStream.Read(Sector, cSectorSize);
    Move(Sector.dwAddress[0], SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);
    FillChar(Sector.dwAddress[0], cSectorAddressSize + cSectorModeSize, 0);

    if TIM^.dwSIZE > P then
    begin
      LastPartSize := TIM^.dwSIZE - P;
      Move(TIM^.DATA^[P], Sector.dwData[0], LastPartSize);

      ecc := build_edc(@(Sector.dwSubHeader[0]), cSectorSubHeaderSize + cSectorDataSize);
      Move(ecc, Sector.dwECC, cSectorECCSize);
      encode_L2_P(@(Sector.dwAddress[0]));
      encode_L2_Q(@(Sector.dwAddress[0]));
      BuildAdress(TimSectorNumber, Sector.dwAddress[0]);

      sImageStream.Seek(TimStartSectorPos, soBeginning);
      sImageStream.Write(Sector, cSectorSize);
      sImageStream.Seek(Int64(TimStartSectorPos) + Int64(cSectorHeaderSize), soBeginning);
      sImageStream.Write(SecAddrAndMode[0], cSectorAddressSize + cSectorModeSize);
    end;
  end;

  sImageStream.free;
end;

function ReplaceTimInFile(const FileName, TimToInsert: string; InsertTo: Integer;
  ImageScan: Boolean): Boolean;

var
  SIZE, P: Integer;
  TIM: PTIM;
begin
  Result := False;

  SIZE := FileSizeUTF8(TimToInsert);
  P := 0;
  TIM := LoadTimFromFile(TimToInsert, P, False, SIZE);
  // SaveTimToFile('test.tim', TIM);
  if TIM = nil then
    Exit;

  ReplaceTimInFileFromMemory(FileName, TIM, InsertTo, ImageScan);

  FreeTIM(TIM);
  Result := True;
end;

end.
