unit TagWMA;

interface

uses Windows, KOL, ATLCommon;

{$J+}

const
  WM_TrackNumber = 'WM/TrackNumber';
  WM_StreamType = 'WM/StreamType';
  WM_MCDI = 'WM/MCDI';
  WM_GenreID = 'WM/GenreID';
  WMFSDKNeeded = 'WMFSDKNeeded';

  // Тип тегов (WM_StrTag - строковый; WM_IntTag - целый; WM_Binary - бинарный)
  WM_StrTag = 0;
  WM_IntTag = 3;
  WM_Binary = 1;

type
  //Рекорд для хранения расширенных тегов
  PExTag = ^TExTag;
  TExTag = record
    Name, Value: String;      // Название тега и его значение
    ExTagType: Word;              // Тип тега (WM_StrTag - строковый; WM_IntTag - целый; WM_Binary - бинарный)
    BinBuff: Pointer;             // Буфер с данными для типа поля WM_Binary
    BinSize: Word;                // Размер буфера с данными для типа поля WM_Binary
  end;

{$DEFINE TAG}
{$DEFINE EX_TAG}

{$IFNDEF EX_TAG}
  {$IFNDEF TAG}
    {$DEFINE TAG}
  {$ENDIF}
{$ENDIF}

  PTagWMA = ^TTagWMA;
  TWMATag = PTagWMA;
  TExWMATag = PTagWMA;

  TTagWMA = object(TTagInfo)
  private
    FIsExWMA: Boolean;
{$IFDEF EX_TAG}
    procedure WriteTagExtended(Stream: PStream);
    procedure ReadTagExtended(const Source: PStream);

    function GetExTag(Index: Integer): PExTag;
    function GetExTagCount: Integer;
    function GetExTagByName(Index: String): PExTag;
    function GetExTagByString(Index: String): String;

    function GetTag(const Index: TEasyTags): String; virtual;
    procedure SetTag(const Index: TEasyTags; const Value: String); virtual;
{$ENDIF}

  private
{$IFDEF TAG}
    procedure ReadTagStandard(const Source: PStream);
    procedure WriteTagStandard(Stream: PStream);
{$ENDIF}

    procedure ReadObject(const ID: String; const AObjectSize: Cardinal; Source: PStream);

  public
    constructor Create(AIsExWMA: Boolean);
    destructor Destroy; virtual;
    procedure ResetData; virtual;

    function ReadFromFile(const AFileName: KOLstring): Boolean; virtual;
    function SaveToFile(const AFileName: KOLstring): Boolean; virtual;
    function RemoveFromFile(const AFileName: KOLstring): Boolean; virtual;

{$IFDEF TAG}
    property Rating: String index etRating read GetTag write SetTag;
{$ENDIF}


{$IFDEF EX_TAG}
    // Работа с расширенными тегами
    function AddExTag(AName, AValue: String; AExTagType: Integer): Integer;  // Добавляет тег
    procedure SetExTag(AName, AValue: String; AExTagType: Integer);          // Устанавливает значение тега, если он есть, а если нет - добавляет

    function AddTagEx(AName, AValue: String): Integer;  // Добавляет тег
    procedure SetTagEx(AName, AValue: String);          // Устанавливает значение тега, если он есть, а если нет - добавляет
    procedure DelTagEx(Index: Integer);                                      // Удаляет тег

    property ExTagCount: Integer read GetExTagCount;                         // Количество тегов
    property ExTags[Index: Integer]: PExTag read GetExTag;                    // Теги (список)
    property ExTagByName[Index: String]: PExTag read GetExTagByName;         // Теги (по имени)
    property ExTagByNameString[Index: String]: String read GetExTagByString; // Строковое значени тега (по имени)
{$ENDIF}
  end;

{$IFDEF TAG}
  function NewTagWMA: TWMATag;
{$ENDIF}
{$IFDEF EX_TAG}
  function NewTagExWMA: TExWMATag;
{$ENDIF}

  function ReadFieldString(const Source: PStream; DataSize: Word): String;
  function ReadFieldInteger(const Source: PStream; DataSize: Word): Int64;

const
  WMA_HEADER_ID = #48#38#178#117#142#102#207#17#166#217#0#170#0#98#206#108;
  WMA_FILE_PROPERTIES_ID = #161#220#171#140#71#169#207#17#142#228#0#192#12#32#83#101;
  WMA_STREAM_DESCRIPTION_ID = #64#82#209#134#29#49#208#17#163#164#0#160#201#3#72#246;
  WMA_STREAM_PROPERTIES_ID = #145#7#220#183#183#169#207#17#142#230#0#192#12#32#83#101;
  WMA_DATA_ID = #54#38#178#117#142#102#207#17#166#217#0#170#0#98#206#108;
  WMA_MAX_STRING_SIZE = 4096;

implementation

uses ATLLib;

type
  TObjectID = array [1..16] of Char;

const
  WMA_CONTENT_DESCRIPTION_ID: TObjectID = #51#38#178#117#142#102#207#17#166#217#0#170#0#98#206#108;
  WMA_EXTENDED_CONTENT_DESCRIPTION_ID: TObjectID = #64#164#208#210#7#227#210#17#151#240#0#160#201#94#168#80;
  WMATags: array [TEasyTags] of String = ('WM/Track', 'WM/Title', 'WM/Author', 'WM/AlbumTitle', 'WM/Year', '', 'WM/Genre',
                                           'WM/Composer', 'WM/Encoder', 'WM/Copyright', 'WM/PromotionURL', 'WM/Description', '',
                                           'WM/Lyrics', '', '', '', 'WM/Rating', 'WMFSDKVersion');

function NewTagWMA: TWMATag;
begin
    New(Result, Create(False));
end;

function NewTagExWMA: TExWMATag;
begin
    New(Result, Create(True));
end;

constructor TTagWMA.Create;
begin
{$IFDEF EX_TAG}
  {$IFNDEF TAG}
    FIsExWMA:= True;
  {$ELSE}
    FIsExWMA:= AIsExWMA;
  {$ENDIF}
{$ENDIF}
{$IFNDEF EX_TAG}
  {$IFDEF TAG}
    FIsExWMA:= False;
  {$ENDIF}
{$ENDIF}

    inherited Create;

{$IFDEF TAG}
    if not FIsExWMA then begin
      FExistsTags:= [etTitle, etArtist, etComment, etCopyright, etRating];
      FTagVendor:= tWMA;
    end;
{$ENDIF}

{$IFDEF EX_TAG}
    if FIsExWMA then begin
      FExistsTags:= [etTrack, etTitle, etArtist, etAlbum, etYear, etGenre, etComposer, etEncoder, etCopyright, etLink, etDescription, etLyrics, etRating, etVersion];
      FTagVendor:= tExWMA;
    end;
{$ENDIF}
end;

procedure TTagWMA.ResetData;
{$IFDEF EX_TAG}
var i: Integer;
{$ENDIF}
begin
{$IFDEF EX_TAG}
    if FIsExWMA then
      for i:= FTagList.Count-1 downto 0 do DelTagEx(i);
{$ENDIF}
    inherited;

{$IFDEF EX_TAG}
    if FIsExWMA then FTagList.Clear;
{$ENDIF}
end;

destructor TTagWMA.Destroy;
begin
{$IFDEF EX_TAG}
    if FIsExWMA then ResetData;
{$ENDIF}
    inherited;
end;

function TTagWMA.ReadFromFile(const AFileName: KOLstring): Boolean;
var Source: PStream;
    ID: TObjectID;
    i, ObjectCount, ObjectSize, Position: Integer;
begin
    Result:= False;
    try
      ResetData;
      Source := OpenFileRead(AFileName);
      try
        if Integer(Source.Handle)<=0 then Exit;

        Source.Read(ID, SizeOf(ID));
        if ID = WMA_HEADER_ID then begin
          Source.Seek(8, spCurrent);
          Source.Read(ObjectCount, SizeOf(ObjectCount));
          Source.Seek(2, spCurrent);

          for i := 1 to ObjectCount do begin
            Position := Source.Position;

            Source.Read(ID, SizeOf(ID));
            Source.Read(ObjectSize, SizeOf(ObjectSize));
            ReadObject(ID, ObjectSize, Source);

            Source.Seek(Position + ObjectSize, spBegin);
          end;
        end;

        Result := true;
      finally
        Source.Free;
      end;
    except
    end;
end;

procedure TTagWMA.ReadObject(const ID: String; const AObjectSize: Cardinal; Source: PStream);
begin
{$IFDEF TAG}
    if not FIsExWMA then
      if ID = WMA_CONTENT_DESCRIPTION_ID then begin
        Source.Seek(4, spCurrent);
        FExists := True;
        ReadTagStandard(Source);
        FTagSize:= FTagSize + AObjectSize;
      end;
{$ENDIF}

{$IFDEF EX_TAG}
    if FIsExWMA then
      if ID = WMA_EXTENDED_CONTENT_DESCRIPTION_ID then begin
        Source.Seek(4, spCurrent);
        FExists:= True;
        ReadTagExtended(Source);
        FTagSize:= FTagSize + AObjectSize;
      end;
{$ENDIF}
end;


function TTagWMA.RemoveFromFile(const AFileName: KOLstring): Boolean;
begin
    ResetData;
    Result := SaveToFile(AFileName);
end;

function TTagWMA.GetTag(const Index: TEasyTags): String;
begin
{$IFDEF EX_TAG}
    if FIsExWMA then
      if WMATags[Index] <> '' then Result:= ExTagByNameString[WMATags[Index]] else Result:= '';
{$ENDIF}
{$IFDEF TAG}
    if not FIsExWMA then
      Result:= inherited GetTag(Index);
{$ENDIF}
end;

procedure TTagWMA.SetTag(const Index: TEasyTags; const Value: String);
begin
{$IFDEF EX_TAG}
    if FIsExWMA then
      if WMATags[Index] <> '' then SetTagEx(WMATags[Index], Value);
{$ENDIF}
{$IFDEF TAG}
    if not FIsExWMA then
      inherited SetTag(Index, Value);
{$ENDIF}
end;

type TRecordID = record Index : Cardinal; Offset : Cardinal; Size : Int64; end;

function TTagWMA.SaveToFile(const AFileName: KOLstring): Boolean;
  function GetPosition_DataID(AStream: PStream): Int64;
  var ID : TObjectID;
      Position : Cardinal;
  begin
      AStream.Read(ID, SizeOf(ID));
      if WMA_DATA_ID <> ID then begin
        AStream.Seek(0, spBegin);
        while AStream.Position < AStream.Size-16 do begin
          AStream.Read(ID, SizeOf(ID));
          if WMA_DATA_ID = ID then Break;
          Position:= AStream.Position - 15;
          AStream.Seek(Position, spBegin);
          if Position > AStream.Size - 15 then begin
            Result:= -1; Exit;
          end;
        end;
      end;
      AStream.Seek(-16, spCurrent);
      Result:= AStream.Position;
  end;

  function GetOffsetID(AStream: PStream; SetID : TObjectID; Index: Cardinal) : TRecordID;
  var  i : Cardinal;
       ID : TObjectID;
       ObjectSize    : Int64;
       Position      : Int64;
  begin
      Result.Index:= 0; Result.Offset:= 30; Result.Size:= 0;
      AStream.Seek(30, spBegin);
      for i:= 1 to Index do begin
        Position:= AStream.Position;
        AStream.Read(ID, SizeOf(ID));
        AStream.Read(ObjectSize, SizeOf(ObjectSize));
        if (ObjectSize < 1) or (ObjectSize > AStream.Size) then Exit;
        if ID = SetID then begin
          Result.Index:= i;
          Result.Offset:= Position;
          Result.Size:= ObjectSize;
          Break;
        end;
        AStream.Seek(ObjectSize - 16 - 8, spCurrent);
      end;
  end;

var Source, Destination: PStream;
    ID: TObjectID;
    SlackSpaceA, SlackSpaceB : Cardinal;
    HeadTAG, DataTAG, _ExtTAG, _TAG: TRecordID;
    Position: Int64;
begin
    Result:= False;
    try
      Source:= OpenFileWrite(AFileName);
      Destination:= CreateFile(AFileName+'~');
      try
        Source.Read(ID, SizeOf(ID));
        if ID = WMA_HEADER_ID then begin

          Source.Read(HeadTAG.Size, SizeOf(HeadTAG.Size));      {Total TAG Size}
          Source.Read(HeadTAG.Index, SizeOf(HeadTAG.Index));    {Total TAG Fields}

          //Получаем информацию о секции DATA_PART
          Source.Seek(HeadTAG.Size, spBegin);
          DataTAG.Offset:= GetPosition_DataID(Source);
          if DataTAG.Offset = 0 then Exit;
          DataTAG.Size:= Source.Size - Source.Position;

          // Получаем информацию о секции тегов и расширенных тегов
          _ExtTAG:= GetOffsetID(Source, WMA_EXTENDED_CONTENT_DESCRIPTION_ID, HeadTAG.Index);
          _TAG:= GetOffsetID(Source, WMA_CONTENT_DESCRIPTION_ID, HeadTAG.Index);
          if _ExtTAG.Size = 0 then Inc(HeadTAG.Index, 1);
          if _TAG.Size = 0    then Inc(HeadTAG.Index, 1);

          {Порядок сохранения: ExtTAG, slack, TAG, slack, AudioData}

          {Получаем информацию о Slack Spaces}
          if _ExtTAG.Offset > _TAG.Offset then begin                       {Tag Is FIRST}
            SlackSpaceA:= _ExtTAG.Offset - (_TAG.Offset + _TAG.Size);
            SlackSpaceB:= DataTAG.Offset- (_ExtTAG.Offset+_ExtTAG.Size);
            Position:= _TAG.Offset;                                  
          end
          else
            if _ExtTAG.Offset < _TAG.Offset then begin               {ExtendTag Is FIRST}
              SlackSpaceA:= _TAG.Offset  -(_ExtTAG.Offset + _ExtTAG.Size);
              SlackSpaceB:= HeadTAG.Size-(_TAG.Offset + _TAG.Size);
              Position:= _ExtTAG.Offset;
            end
            else Exit;

          {Копируем начало файла их исходного в конечный}
          Source.Seek(0, spBegin);
          Stream2Stream(Destination, Source, Position);

          {Сохраняем ExtTAG}
{$IFDEF EX_TAG}
          if FIsExWMA then WriteTagExtended(Destination);
{$ENDIF}
{$IFDEF TAG}
          if not FIsExWMA then begin
            Source.Seek(_ExtTAG.Offset, spBegin);
            Stream2Stream(Destination, Source, _ExtTAG.Size);
          end;
{$ENDIF}

          {Записываем Slack Space}
          if SlackSpaceA > 16 then begin
            if _ExtTAG.Offset>_TAG.Offset then Source.Seek(_TAG.Offset+_TAG.Size, spBegin)
            else Source.Seek(_ExtTAG.Offset+_ExtTAG.Size, spBegin);
            Stream2Stream(Destination, Source, SlackSpaceA);
          end;

          {Сохраняем StandardTAG}

{$IFDEF TAG}
          if not FIsExWMA then WriteTagStandard(Destination);
{$ENDIF}
{$IFDEF EX_TAG}
          if FIsExWMA then begin
            Source.Seek(_TAG.Offset, spBegin);
            Stream2Stream(Destination, Source, _TAG.Size);
          end;
{$ENDIF}

          {Записываем Slack Space}
          if SlackSpaceB > 16 then begin
            if _ExtTAG.Offset>_TAG.Offset then Source.Seek(_ExtTAG.Offset+_ExtTAG.Size, spBegin)
            else Source.Seek(_TAG.Offset+_TAG.Size, spBegin);
            Stream2Stream(Destination, Source, SlackSpaceB);
          end;

          {Сохраняем Audio DATA}
          Position:= Destination.Position;
          Source.Seek(DataTAG.Offset, spBegin);
          Stream2Stream(Destination, Source, DataTAG.Size);

          {Записываем длину Tag в начало файла}
          Destination.Seek(16, spBegin);
          Destination.Write(Position, SizeOf(Position));

          {Записываем количество полей}
          Destination.Write(HeadTAG.Index, SizeOf(HeadTAG.Index));

          {Записываем контрольную длины файла}
          SlackSpaceA:= Destination.Size;
          DataTAG:= GetOffsetID(Destination, WMA_FILE_PROPERTIES_ID, HeadTAG.Index);
          Destination.Seek(DataTAG.Offset+16+4+20, spBegin);
          Destination.Write(SlackSpaceA, SizeOf(SlackSpaceA));
        end
        else Exit;

      finally
        Source.Free; Destination.Free;
      end;
      Result:= DeleteFile(PKOLChar(AFileName)) and MoveFile(PKOLChar(AFileName+'~'), PKOLChar(AFileName));
      if FileExists(AFileName+'~') then DeleteFile(PKOLChar(AFileName+'~'));
    except
    end;
end;

{$IFDEF EX_TAG}

procedure TTagWMA.ReadTagExtended(const Source: PStream);
var i, k, FieldCount, DataSize, DataType: Word;
    FieldName: String;
begin
    Source.Read(FieldCount, SizeOf(FieldCount));
    for i := 1 to FieldCount do begin
      Source.Read(DataSize, SizeOf(DataSize));
      FieldName := ReadFieldString(Source, DataSize);
      Source.Read(DataType, SizeOf(DataType));
      Source.Read(DataSize, SizeOf(DataSize));

      case DataType of
        0: AddExTag(FieldName, ReadFieldString(Source, DataSize), DataType);
        3: AddExTag(FieldName, Int2Str(ReadFieldInteger(Source, DataSize)), DataType);
        else begin
          k:= AddExTag(FieldName, '', DataType);
          ExTags[k].BinBuff:= AllocMem(DataSize);
          Source.Read(ExTags[k].BinBuff^, DataSize);
        end;
      end;
    end;
end;

procedure TTagWMA.WriteTagExtended(Stream: PStream);
  procedure WriteStr(AName : String);
  var FieldSize: Word;
      FieldData: array [1..WMA_MAX_STRING_SIZE * 2] of Byte;
  begin
      FieldSize:= Min(WMA_MAX_STRING_SIZE, Length(AName)*2 + 2);
      Stream.Write(FieldSize, SizeOf(FieldSize));
      StringToWideChar(AName, PWideChar(@FieldData), FieldSize - 1);
      FieldData[FieldSize-1]:= 0; FieldData[FieldSize]:= 0;
      Stream.Write(FieldData, FieldSize);
  end;
var i: Integer;
    NewObjectSize : Int64;
    IntField, FieldCount: Word;
    Position: Cardinal;
begin
    Position:= Stream.Position;
    FieldCount:= FTagList.Count;
    Stream.Write(WMA_EXTENDED_CONTENT_DESCRIPTION_ID, 16);
    Stream.Write(WMA_EXTENDED_CONTENT_DESCRIPTION_ID, 8);
    Stream.Write(FieldCount, 2);

    for i := 0 to FieldCount-1 do begin
      WriteStr(ExTags[i].Name);
      Stream.Write(ExTags[i].ExTagType, SizeOf(ExTags[i].ExTagType));
      case ExTags[i].ExTagType of
        WM_StrTag:WriteStr(ExTags[i].Value);
        WM_IntTag: begin
          IntField:= 4;
          Stream.Write(IntField, SizeOf(IntField));
          IntField:= Str2Int(ExTags[i].Value);
          Stream.Write(IntField, SizeOf(IntField));
          IntField:= 0;
          Stream.Write(IntField, SizeOf(IntField));
        end;
        WM_Binary: begin
          Stream.Write(ExTags[i].BinSize, SizeOf(ExTags[i].BinSize));
          Stream.Write(ExTags[i].BinBuff, ExTags[i].BinSize);
        end;
      end;
    end;

    {Write Size To OutPut sStrream}
    NewObjectSize := Stream.Position - Position;
    Stream.Seek(Position + 16, spBegin);
    Stream.Write(NewObjectSize, SizeOf(NewObjectSize));
    Stream.Seek(0, spEnd);
end;

function TTagWMA.GetExTag(Index: Integer): PExTag;
begin
    Result:= PExTag(FTagList.Objects[Index]);
end;

function TTagWMA.AddExTag(AName, AValue: String; AExTagType: Integer): Integer;
var _ExTag: PExTag;
begin
    New(_ExTag); _ExTag.Name:= AName; _ExTag.Value:= AValue; _ExTag.ExTagType:= AExTagType; _ExTag.BinBuff:= nil; _ExTag.BinSize:= 0;
    FTagList.AddObject('', Integer(_ExTag));
    Result:= FTagList.Count-1;
end;

procedure TTagWMA.DelTagEx(Index: Integer);
var eTag: PExTag;
begin
    eTag:= ExTags[Index];
    eTag.Name:= ''; eTag.Value:= '';
    DisposeMem(eTag.BinBuff);
    Dispose(eTag);
    FTagList.Delete(Index);
end;

function TTagWMA.GetExTagCount: Integer;
begin
    Result:= FTagList.Count;
end;

function TTagWMA.GetExTagByName(Index: String): PExTag;
var i: Integer;
begin
    Result:= nil;
    for i:= 0 to FTagList.Count-1 do
      if AnsiUpperCase(ExTags[i].Name) = AnsiUpperCase(Index) then begin
        Result:= ExTags[i];
        Break;
      end;
end;

function TTagWMA.GetExTagByString(Index: String): String;
var _ExTag: PExTag;
begin
    Result:= '';
    _ExTag:= ExTagByName[Index];
    if (_ExTag <> nil) then Result:= _ExTag.Value;
end;

procedure TTagWMA.SetExTag(AName, AValue: String; AExTagType: Integer);
var _ExTag: PExTag;
begin
    _ExTag:= ExTagByName[AName];
    if _ExTag <> nil then _ExTag.Value:= AValue
    else AddExTag(AName, AValue, AExTagType);
end;

function TTagWMA.AddTagEx(AName, AValue: String): Integer;
begin
    Result:= AddExTag(AName, AValue, WM_StrTag);
end;

procedure TTagWMA.SetTagEx(AName, AValue: String);
begin
    SetExTag(AName, AValue, WM_StrTag);
end;

{$ENDIF}



{$IFDEF TAG}

procedure TTagWMA.ReadTagStandard(const Source: PStream);
var i: Integer;
    FieldSize: array [1..5] of Word;
    FieldValue: String;
begin
    Source.Read(FieldSize, SizeOf(FieldSize));
    for i:= 1 to 5 do
      if FieldSize[i] > 0 then begin
        FieldValue:= ReadFieldString(Source, FieldSize[i]);
        case i of
          1: Title:= FieldValue;
          2: Artist:= FieldValue;
          3: Copyright:= FieldValue;
          4: Comment:= FieldValue;
          5: Tags[etRating]:= FieldValue;
        end;
        FExists:= True;
      end;
end;

procedure TTagWMA.WriteTagStandard(Stream: PStream);
  procedure Write_Field(Text : String);
  var FieldSize: Word;
      FieldData: array [1..WMA_MAX_STRING_SIZE * 2] of Byte;
  begin
      FieldSize:= Min(WMA_MAX_STRING_SIZE, Length(Text)*2 + 2);
      StringToWideChar(Text, PWideChar(@FieldData), FieldSize - 1);
      FieldData[FieldSize-1]:= 0; FieldData[FieldSize]:= 0;
      Stream.Write(FieldData, FieldSize);
  end;
var  FieldSize : array [1 .. 5] of Word;
     Size      : Int64;
begin
    Size:= 0;
    FieldSize[1]:= Min(WMA_MAX_STRING_SIZE, Length(Title)* 2 + 2);
    FieldSize[2]:= Min(WMA_MAX_STRING_SIZE, Length(Artist)* 2 + 2);
    FieldSize[3]:= Min(WMA_MAX_STRING_SIZE, Length(Copyright)*2 + 2);
    FieldSize[4]:= Min(WMA_MAX_STRING_SIZE, Length(Comment)*2 + 2);
    FieldSize[5]:= Min(WMA_MAX_STRING_SIZE, Length(Tags[etRating])*2 + 2);
    Size:= 16 + 8 + 5 + 5 + FieldSize[1]+FieldSize[2]+FieldSize[3]+FieldSize[4]+FieldSize[5];

    Stream.Write(WMA_CONTENT_DESCRIPTION_ID, 16);
    Stream.Write(Size, SizeOf(Size));
    Stream.Write(FieldSize, SizeOf(FieldSize));

    Write_Field(Title);
    Write_Field(Artist);
    Write_Field(Copyright);
    Write_Field(Comment);
    Write_Field(Tags[etRating]);
end;

{$ENDIF}

function ReadFieldString(const Source: PStream; DataSize: Word): String;
var StringSize: Integer;
    FieldData: array [1..WMA_MAX_STRING_SIZE * 2] of Byte;
begin
    Result := '';
    StringSize := DataSize div 2;
    if StringSize > WMA_MAX_STRING_SIZE then StringSize := WMA_MAX_STRING_SIZE;
    Source.Read(FieldData, StringSize * 2);
    Source.Seek(DataSize - StringSize * 2, spCurrent);
    Result:= WideCharLenToString(PWideChar(@FieldData), StringSize-1);
end;

function ReadFieldInteger(const Source: PStream; DataSize: Word): Int64;
var DataBits: Integer;
begin
    Result := 0;
    DataBits := DataSize div 2;
    if DataBits > 8 then DataBits:= 8;                {max int64}
    case DataBits of
      1, 2, 4, 8: Source.Read(Result, DataBits);
(*      1 : Source.Read(Result, SizeOf(Byte));          {1byte}
      2 : Source.Read(Result, SizeOf(Word));          {2byte}
      4 : Source.Read(Result, SizeOf(Cardinal));      {4byte}
      8 : Source.Read(Result, SizeOf(Int64));         {8byte}*)
      else result := 0;
    end;
    Source.Seek(DataSize - DataBits, spCurrent);
end;

end.

