////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Works with RIFF files.                                                     //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20020117 - Creation                                                        //
// 20020122 - Test Ok, documented                                             //
// 20030326 - Struggle for safety (add some try/except checks)...             //
// 20061218 - add sig_tbtu                                                    //
// 20230530 - Modified for FPC (A.K.)                                         //
// 20240620 - rsmf_FixUtf8 in ReadString                                      //
////////////////////////////////////////////////////////////////////////////////

unit _crw_riff;

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 {$IFDEF WINDOWS} messages, {$ENDIF}
 sysutils, classes, graphics, controls, forms,
 _crw_alloc, _crw_fpu, _crw_rtc, _crw_fifo, _crw_str, _crw_fio, _crw_plut,
 _crw_utf8, _crw_ef;

 {
 *******************************************************************************
 Обшее описание формата RIFF и работы с ним.
 *******************************************
 Введение.
 *********
        Формат файлов RIFF - Resource Interchange File Format -  это, скорее,
 не конкретный формат файлов, а принцип построения двоичных файлов сложной
 структуры, достаточно свободно расширяемый по мере надобности и, в отличие
 от более сложных форматов потоковой модели объектов, как в Delphi,
 допускающий простое формальное описание, а следовательно, более простую
 переносимость.
        Основой RIFF служит блочная (chunk) структура. Блок состоит из заголовка
 фиксированной структуры и данных, структура которых определяется заголовком.
 Заголовок  содержит всего два поля:
                Signature - LongInt - сигнатура или идентификатор блока
                Size      - LongInt - размер массива данных в байтах
        Данные, в общем случае неопределенной структуры, в зависимости от заголовка,
 могут включать в себя другие блоки, что позволяет строить сложные вложенные
 (рекурсивные) структуры.
        Сигнатура блока, собственно, определяет, как надо интерпретировать данные,
 которые этот блок содержит. Поле Size позволяет перейти к следующему блоку.
 За счет этого те блоки, которые не распознаются программой, можно просто
 пропускать. Это позволяет легко расширять формат за счет добавления новых
 блоков без потери совместимости со старыми программами.
 Простые блоки и формы.
 **********************
 Можно выделить два вида блоков:
 1) Простой блок (chunk) имеет вид:
    | Signature |     Size    |     Data     |
     <--------->  <--------->  <------------>
       LongInt      LongInt      Size bytes
    Массив данных рассматривается в общем случае как бесструктурный массив байтов.
    Если программа чтения не распознает сигнатуру блока, блок должен быть пропущен.
    Это правило позволяет расширять формат без потери работоспособности старых
    версий программ, которые ничего не знают о новых полях. Интерпретация блока
    также зависит от того, в какую форму она входит (смотри далее).
 2) Форма (Form) - частный случай блока, обеспечивающий создание "вложенных"
    (рекурсивных) структур.
    | Signature |     Size    |     Data                                 |
     <--------->  <--------->  <---------------------------------------->
       LongInt      LongInt      Size bytes
                              | FormID   | Chunk 1 |  ...     | Chunk N  |
                               <-------->
                                 LongInt
    Форма является блоком, на структуру данных которого наложены такие условия:
    a) Первым элементом данных является идентификатор формы FormID : LongInt
    b) За идентификатором следует список блоков, содержащихся в форме.
       В частности, блок может быть формой и может содержать другие блоки
       и формы и т.д. Назовем блоки, находящиеся внутри формы, дочерними, а
       форму, соответственно, родительской для этих блоков.
 Таким образом, блоки идентифицируются своей сигнатурой, а формы - сигнатурой
 и идентификатором формы. Например, WAVE - файлы содержат форму RIFF WAVE,
 которая содержит блоки fmt, data и т.д. Отметим также, что интерпретация блока
 зависит от контеста, то есть от того, в какой форме он встретился (какова его
 родительская форма). Можно отображать структуру блоков в виде древовидной
 структуры, в которой смещение служит для обозначения вложенности блока. Тогда
 структура WAVE-файла выглядит как:
  RIFF WAVE
   fmt
   data
   fact
    ...
 Форма RIFF.
 ***********
        Каждый файл RIFF обязан содержать форму с сигнатурой RIFF, которая служит
 родительской формой для всех остальных форм и блоков. Это, в частности,
 означает, что поле Size формы RIFF должно содержать число (размер файла-8).
 Говоря по простому, RIFF-файл - это просто одна RIFF-форма, которая содержит
 в себе все все остальное. Для AVI файлов это не так (не знаю почему), но в
 данной библиотеке считается, что неверное значение означает повреждение файла.
 На этом основан метод RiffFiler.Validate, который отрезает конец файла по
 границе формы RIFF, так что последняя неудачная запись данных в конец файла
 аннулируется.
        Идентификатор формы RIFF фактически определяет тип файла RIFF. Например,
 для WAV - файлов это WAVE, для AVI - AVI и т.д. Программа чтения распознает
 корректность формата по признаку наличия сигнатуры RIFF, нужного идентификатора
 формы и правильного размера RIFF-формы.
 Правила именования блоков.
 **************************
 Сигнатуры и идентификаторы форм обычно содержат 4-байтные числа, получаемые из
 читабельных мнемоник, облегчающих их интерпретацию. Для генерации сигнатур
 можно взять утилиту CRW_DAQ\Tools\Magic\Magic.Exe.
 Судя по всему, сигнатуры форм идентифицируются сигнатурами из заглавных букв,
 а сигнатуры простых блоков - прописными буквами. У меня нет информации,
 является это правило частью спецификации RIFF или это просто удобная практика.
 Однако я придерживаюсь этой практики. Это позволяет, например, делать листинг
 структуры файла без вникания в подробности.
 Чтение RIFF-файла.
 ******************
 Основано на процедуре ForEach. Надо написать процедуру, которая что-то делает
 для каждого блока в зависимости от его сигнатуры и вызвать ForEach.
 При вызове процедуры файл позиционирован на начало данных блока, так что можно
 сразу читать данные.
 Вот пример чтения списка форм файла:
  procedure ListItem(Filer:TRiffFiler; const Chunk:TRiffChunk; var Terminate:Boolean; Custom:Pointer);
  begin
   Echo(Format('%d %d',[Chunk.dwSign, Chunk.dwFormID]));
  end;
  begin
   Filer:=NewRiffFiler(..);
   Filer.ForEach(ListItem,Filer.RootForm,nil);
   Kill(Filer);
  end.
  Процедура ForEach может вызываться рекурсивно для чтения форм внутри других
  форм.
 Запись в RIFF-файл.
 *******************
 Типичная процедура записи в файл новой формы имеет такой вид:
  Filer:=NewRiffFiler(..);                // create filer
  Filer.SeekChunkNext(Filer.RootForm);    // seek to end of file
   Form:=Filer.CreateForm(..)             // start writing form
    Chunk:=Filer.CreateChunk(..)          // start writing chunk inside form
     Filer.Write(..)                      // write chunk data
    Filer.FlusChunk(Chunk)                // stop writing chunk, update chunk header
    SubForm:=Filer.CreateForm(..)         // start writing subform inside form
     ...                                  // do the same inside subform
    Filer.FlushChunk(SubForm)             // stop writing subform inside form
    ...                                   // write another chunks and subforms inside form
   Filer.FlushChunk(Form)                 // update form header
  Filer.Flush;                            // stop writing, update root RIFF form header
  Kill(Filer);                            // destroy filer
 В любом случае операция записи блока помещается в "скобки" из вызовов Create...Flush.
 Это главное правило, которое должно соблюдаться неукоснительно.
 Описание формата CRW.
 *********************
        Файл CRW содержит родительскую форму RIFF CRWF, включающую в себя все
 остальные блоки и формы. Правило Size=(размер файла-8) считается обязательным
 и служит для восстановления данных после неудачной записи.
        Форма RIFF CRW содержит в себе список форм ITEM XXXX, где XXXX - тип
 объекта. Формы ITEM XXXX могут содержать внутренние формы ITEM YYYY и так далее.
 Родительская форма определяет контекст интерпретации блоков, например, блок name
 может означать имя окна или имя кривой в окне.
 Объекты основной формы RIFF CRWF - это окна:
  ITEM CRVW - окно с кривыми
  ITEM SURW - окно с графиком поверхности
  и т.д.
  Окна могут содержать другие формы ITEM, например, форма ITEM CRVW содержит
  список форм ITEM CURV, которые соответствуют кривым этого окна.
  Можно условно отобразить формат CRW в виде такой структуры:
   RIFF CRWF      : Form main
    ITEM CRVW     : Form  curve window
     name         : Chunk window name                   as String
     time         : Chunk save time in msec from Xmas   as Double
     vers         : Chunk window program version        as LongInt
     ltwh         : Chunk window left,top,width,height  as LongInt * 4
     titl         : Chunk window title                  as String
     lgnd         : Chunk window legend                 as String
     text         : Chunk window comment text           as String
     clrp         : Chunk window color palette          as Longint * 8
     roi          : Chunk window ROI markers            as Double * 4
     wrld         : Chunk window world (limits)         as Double * 4
     ITEM CURV    : Form curve (subform of ITEM CRVW)
      name        : Chunk curve name                    as String
      clrp        : Chunk curve color                   as LongInt
      styl        : Chunk curve style                   as LongInt
      step        : Chunk curve step                    as LongInt
      data        : Chunk array of curve points         as Double (x,y) points array
      text        : Chunk curve comment text            as String
     defc         : Chunk window default curve index    as LongInt
    ITEM SUFW     : Form  surface window
     name         : Chunk window name                   as String
     time         : Chunk save time in msec from Xmas   as Double
     vers         : Chunk window program version        as LongInt
     ltwh         : Chunk window left,top,width,height  as LongInt * 4
     titl         : Chunk window title                  as String
     lgnd         : Chunk window legend                 as String
     text         : Chunk window comment text           as String
     clrp         : Chunk window color palette          as Longint * 7
     lims         : Chunk window limits                 as Double * 4
     clip         : Chunk window clipping               as Double * 4
     data         : Chunk Z-matrix                      as LongInt Rows,Columns, Double Rows*Columns Z[i,j]
     view         : Chunk window view params            ad double * 5 + longint
 Блоки окон name, time, vers, ltwh являются обязательными, по ним строится
 список объектов архива.
 Исключения.
 ***********
 Объект имеет защищенный конструктор/деструктор NewRiffFiler/Kill, а также
 защиту методов по nil. Другие методы не защищены - перехватывать исключения
 должны клиенты.
 Успеха!
 *******************************************************************************
 }

 {
 Known RIFF files signatures uses in WAV, AVI.
 }
const
 sig_RIFF =  $46464952;   { 'RIFF' - dwSign   - uses always }
 sig_WAVE =  $45564157;   { 'WAVE' - dwFormID - identify WAVE files }
 sig_AVI_ =  $20495641;   { 'AVI ' - dwFormID - identify AVI files }
 sig_LIST =  $5453494C;   { 'LIST' }
 sig_DISP =  $50534944;   { 'DISP' }
 sig_DOS_ =  $20532F44;   { 'DOS ' }
 sig_MEM_ =  $204D454D;   { 'MEM ' }
 sig_fmt_ =  $20746D66;   { 'fmt ' }
 sig_data =  $61746164;   { 'data' - dwSign   - also use in CRW }
 sig_fact =  $74636166;   { 'fact' }
 sig_cue_ =  $20657563;   { 'cue ' }
 sig_plst =  $74736C70;   { 'plst' }
 sig_wavl =  $6C766177;   { 'wavl' }
 sig_slnt =  $746E6C73;   { 'slnt' }
 sig_hdrl =  $6C726468;   { 'hdrl' }
 sig_avih =  $68697661;   { 'avih' }
 sig_strl =  $6C727473;   { 'strl' }
 sig_strh =  $68727473;   { 'strh' }
 sig_strf =  $66727473;   { 'strf' }
 sig_movi =  $69766F6D;   { 'movi' }
 sig_rec_ =  $20636572;   { 'rec ' }
 sig_junk =  $6B6E756A;   { 'junk' }
 sig_idxl =  $6C786469;   { 'idxl' }

 {
 Special CRW signatures
 }
const
 sig_CRWF =  $46575243;   { 'CRWF' - dwFormID - CRW Form = list of ITEM forms  }
 sig_ITEM =  $4D455449;   { 'ITEM' - dwSign   - item of CRW form list          }
 sig_CRVW =  $57565243;   { 'CRVW' - dwFormID - ITEM is CuRVe Window           }
 sig_SURW =  $57525553;   { 'SURW' - dwFormID - ITEM is SURface Window         }
 sig_CURV =  $56525543;   { 'CURV' - dwFormID - ITEM is CURVe                  }
 sig_name =  $656D616E;   { 'name' - dwSign   - ITEM name                      }
 sig_time =  $656D6974;   { 'time' - dwSign   - ITEM save time as double       }
 sig_vers =  $73726576;   { 'vers' - dwSign   - version as longint             }
 sig_text =  $74786574;   { 'text' - dwSign   - ITEM associated text as string }
 sig_titl =  $6C746974;   { 'titl' - dwSign   - ITEM title as string           }
 sig_lgnd =  $646E676C;   { 'lgnd' - dwSign   - ITEM legend as string          }
 sig_clrp =  $70726C63;   { 'clrp' - dwSign   - ITEM color palette as LongInts }
 sig_roi_ =  $20696F72;   { 'roi ' - dwSign   - ITEM ROI as 4*double           }
 sig_step =  $70657473;   { 'step' - dwSign   - curve step                     }
 sig_styl =  $6C797473;   { 'styl' - dwSign   - curve style                    }
 sig_ltwh =  $6877746C;   { 'ltwh' - dwSign   - ITEM left,top,width,height     }
 sig_defc =  $63666564;   { 'defc' - dwSign   - default curve as LongInt       }
 sig_wrld =  $646C7277;   { 'wrld' - dwSign   - ITEM world as TRect2D          }
 sig_0001 =  $31303030;   { '0001' -            for versions                   }
 sig_lims =  $736D696C;   { 'lims' - dwSign   - ITEM limits                    }
 sig_clip =  $70696C63;   { 'clip' - dwSign   - ITEM clip                      }
 sig_view =  $77656976;   { 'view' - dwSign   - ITEM view params               }
 sig_tbtu =  $75746274;   { 'tbtu' - dwSign   - ITEM time base, time units     }

 {
 ReadString Mode flags
 }
const
 rsmf_FixUtf8  = $00000001; { ReadString uses utf8_fix_cp to fix UTF8 codepage }

 {
 RIFF files chunk description
 }
type
 TRiffChunk = packed record
  dwSign    : LongInt;      { chunk signature        }
  dwSize    : LongInt;      { chunk data size        }
  dwFormID  : LongInt;      { form type signature    }
  dwOffset  : LongInt;      { offset of chank        }
 end;

 {
 Small utilities
 }
function dwStr2Sign(Str:LongString):LongInt;
function dwSign2Str(Sign:LongInt):LongString;
function RiffChunk(aSign,aSize,aFormID,aOffset:LongInt):TRiffChunk;
function RiffChunkLimit(const aChunk:TRiffChunk):LongInt;

 {
 General class to work with RIFF files
 }
type
 TRiffFiler = class;
 TRiffFilerForEachAction = procedure(Filer     : TRiffFiler;
                               const Chunk     : TRiffChunk;
                                var  Terminate : Boolean;
                                     Custom    : Pointer);
 TRiffFiler = class(TMasterObject)
 private
  myStream    : TFileStream;
  myFileName  : LongString;
  myFileMode  : Word;
  myModified  : Boolean;
  myRootForm  : TRiffChunk;
  function    GetPosition:LongInt;
  procedure   SetPosition(aPosition:LongInt);
  function    GetSize:LongInt;
  procedure   SetSize(aSize:LongInt);
  function    GetFileName:LongString;
  function    GetFileMode:Word;
  function    GetModified:Boolean;
  procedure   SetModified(aModified:Boolean);
  function    GetRootForm:TRiffChunk;
 protected
  function    CheckOk:Boolean; override;
 public
  constructor Create(const aFileName:LongString; aFileMode:Word; aFormID:LongInt);
  destructor  Destroy; override;
 public
  property    Position:LongInt     read GetPosition write SetPosition;
  property    Size:LongInt         read GetSize     write SetSize;
  property    FileName:LongString  read GetFileName;
  property    FileMode:Word        read GetFileMode;
  property    Modified:Boolean     read GetModified write SetModified;
  property    RootForm:TRiffChunk  read GetRootForm;
 public
  procedure   Read(var Buffer; Count:LongInt);
  procedure   Write(const Buffer; Count:LongInt);
  function    ReadByte:Byte;
  procedure   WriteByte(Value:Byte);
  function    ReadWord:Word;
  procedure   WriteWord(Value:Word);
  function    ReadLongInt:LongInt;
  procedure   WriteLongInt(Value:LongInt);
  function    ReadInt64:Int64;
  procedure   WriteInt64(Value:Int64);
  function    ReadDouble:Double;
  procedure   WriteDouble(Value:Double);
  function    ReadPoint2I:TPoint2I;
  procedure   WritePoint2I(const Value:TPoint2I);
  function    ReadPoint2D:TPoint2D;
  procedure   WritePoint2D(const Value:TPoint2D);
  function    ReadRect2I:TRect2I;
  procedure   WriteRect2I(const Value:TRect2I);
  function    ReadRect2D:TRect2D;
  procedure   WriteRect2D(const Value:TRect2D);
  function    ReadString(aLength:LongInt; aMode:Integer=0; aCP:Integer=0):LongString;
  procedure   WriteString(const Value:LongString);
  procedure   SeekChunk(const aChunk:TRiffChunk; Ofs:LongInt=0);
  procedure   SeekChunkData(const aChunk:TRiffChunk; Ofs:LongInt=0);
  procedure   SeekFormData(const aChunk:TRiffChunk; Ofs:LongInt=0);
  procedure   SeekChunkNext(const aChunk:TRiffChunk; Ofs:LongInt=0);
  function    ReadChunk:TRiffChunk;
  function    ReadForm:TRiffChunk;
  function    ReadChunkNext(const aChunk:TRiffChunk):TRiffChunk;
  function    ReadFormNext(const aChunk:TRiffChunk):TRiffChunk;
  function    CreateChunk(aSign:LongInt):TRiffChunk;
  function    CreateForm(aSign,aFormID:LongInt):TRiffChunk;
  procedure   FlushChunk(var aChunk:TRiffChunk);
  procedure   Flush;
  procedure   ForEach(Action    : TRiffFilerForEachAction;
                const StartForm : TRiffChunk;
                      Custom    : Pointer);
  procedure   Validate;
 end;

 {
 Use NewRiffFiler/Kill rather then Create/Free!
 }
function  NewRiffFiler(const aFileName:LongString; aFileMode:Word; aFormID:LongInt):TRiffFiler;
procedure Kill(var TheObject:TRiffFiler); overload;

implementation

 {
 ******************************
 Small utilities implementation
 ******************************
 }
function dwStr2Sign(Str:LongString):LongInt;
begin
 Result:=0;
 Str:=RightPad(Str,SizeOf(Result),' ');
 SafeMove(Str[1],Result,SizeOf(Result));
end;

function dwSign2Str(Sign:LongInt):LongString;
begin
 Result:='';
 SetLength(Result,SizeOf(Sign));
 SafeMove(Sign,Result[1],SizeOf(Sign));
end;

function RiffChunk(aSign,aSize,aFormID,aOffset:LongInt):TRiffChunk;
begin
 with Result do begin
  dwSign:=aSign;
  dwSize:=aSize;
  dwFormID:=aFormID;
  dwOffset:=aOffset;
 end;
end;

function RiffChunkLimit(const aChunk:TRiffChunk):LongInt;
begin
 with aChunk do Result:=dwOffset + 8 + dwSize;
end;

 {
 *************************
 TRiffFiler implementation
 *************************
 }
function TRiffFiler.CheckOk:Boolean;
begin
 Result:=Assigned(myStream);
end;

constructor TRiffFiler.Create(const aFileName:LongString; aFileMode:Word; aFormID:LongInt);
begin
 inherited Create;
 myStream:=nil;
 myFileName:=aFileName;
 myFileMode:=aFileMode;
 myModified:=false;
 myRootForm:=RiffChunk(0,0,0,0);
 try
  myStream:=TFileStream.Create(aFileName,aFileMode);
 except
  on E:EFCreateError do BugReport(E,Self,'Create');
  on E:EFOpenError   do BugReport(E,Self,'Create');
  else RAISE;
 end;
 if Ok then
 try
  if Size=0 then CreateForm(sig_RIFF,aFormID);
  Position:=0;
  myRootForm:=ReadForm;
  Flush;
  if RootForm.dwSign <> sig_RIFF     then Kill(myStream);
  if RootForm.dwSize < 0             then Kill(myStream);
  if RiffChunkLimit(RootForm) > Size then Kill(myStream);
  if RootForm.dwFormID <> aFormID    then Kill(myStream);
 except
  on E:EReadError  do begin BugReport(E,Self,'Create'); Kill(myStream); end;
  on E:EWriteError do begin BugReport(E,Self,'Create'); Kill(myStream); end;
  else RAISE;
 end;
end;

destructor TRiffFiler.Destroy;
begin
 Flush;
 myFileName:='';
 Kill(myStream);
 inherited Destroy;
end;

function TRiffFiler.GetPosition:LongInt;
begin
 if Ok then Result:=myStream.Position else Result:=0;
end;

procedure TRiffFiler.SetPosition(aPosition:LongInt);
begin
 if Ok then myStream.Position:=aPosition;
end;

function TRiffFiler.GetSize:LongInt;
begin
 if Ok then Result:=myStream.Size else Result:=0;
end;

procedure TRiffFiler.SetSize(aSize:LongInt);
begin
 if Ok then begin
  myStream.Size:=aSize;
  Modified:=true;
 end;
end;

function  TRiffFiler.GetFileName:LongString;
begin
 if Assigned(Self) then Result:=myFileName else Result:='';
end;

function  TRiffFiler.GetFileMode:Word;
begin
 if Assigned(Self) then Result:=myFileMode else Result:=0;
end;

function  TRiffFiler.GetModified:Boolean;
begin
 if Assigned(Self) then Result:=myModified else Result:=false;
end;

procedure TRiffFiler.SetModified(aModified:Boolean);
begin
 if Assigned(Self) then myModified:=aModified;
end;

function  TRiffFiler.GetRootForm:TRiffChunk;
begin
 if Assigned(Self) then Result:=myRootForm else Result:=RiffChunk(0,0,0,0);
end;

procedure TRiffFiler.Read(var Buffer; Count:LongInt);
begin
 if Ok and (@Buffer<>nil) and (Count>0) then myStream.Read(Buffer,Count);
end;

procedure TRiffFiler.Write(const Buffer; Count:LongInt);
begin
 if Ok and (@Buffer<>nil) and (Count>0) then begin
  myStream.Write(Buffer,Count);
  Modified:=true;
 end; 
end;

function TRiffFiler.ReadByte:Byte;
begin
 Result:=0;
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteByte(Value:Byte);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadWord:Word;
begin
 Result:=0;
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteWord(Value:Word);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadLongInt:LongInt;
begin
 Result:=0;
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteLongInt(Value:LongInt);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadInt64:Int64;
begin
 Result:=0;
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteInt64(Value:Int64);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadDouble:Double;
begin
 Result:=0;
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteDouble(Value:Double);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadPoint2I:TPoint2I;
begin
 Result:=Point2I(0,0);
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WritePoint2I(const Value:TPoint2I);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadPoint2D:TPoint2D;
begin
 Result:=Point2D(0,0);
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WritePoint2D(const Value:TPoint2D);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadRect2I:TRect2I;
begin
 Result:=Rect2I(0,0,0,0);
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteRect2I(const Value:TRect2I);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadRect2D:TRect2D;
begin
 Result:=Rect2D(0,0,0,0);
 Read(Result,Sizeof(Result));
end;

procedure TRiffFiler.WriteRect2D(const Value:TRect2D);
begin
 Write(Value,Sizeof(Value));
end;

function TRiffFiler.ReadString(aLength:LongInt; aMode:Integer=0; aCP:Integer=0):LongString;
begin
 Result:='';
 SetLength(Result,aLength);
 Read(PChar(Result)[0],Length(Result));
 if HasFlags(aMode,rsmf_FixUtf8) then Result:=utf8_fix_cp(Result,aCP);
end;

procedure TRiffFiler.WriteString(const Value:LongString);
begin
 Write(PChar(Value)[0],Length(Value));
end;

procedure TRiffFiler.SeekChunk(const aChunk:TRiffChunk; Ofs:LongInt=0);
begin
 Position:=aChunk.dwOffset + Ofs;
end;

procedure TRiffFiler.SeekChunkData(const aChunk:TRiffChunk; Ofs:LongInt=0);
begin
 Position:=aChunk.dwOffset + 8 + Ofs;
end;

procedure TRiffFiler.SeekFormData(const aChunk:TRiffChunk; Ofs:LongInt=0);
begin
 Position:=aChunk.dwOffset + 12 + Ofs;
end;

procedure TRiffFiler.SeekChunkNext(const aChunk:TRiffChunk; Ofs:LongInt=0);
begin
 Position:=aChunk.dwOffset + 8 + aChunk.dwSize + Ofs;
end;

function TRiffFiler.ReadChunk:TRiffChunk;
begin
 with Result do begin
  dwOffset:=Position;
  dwSign:=ReadLongInt;
  dwSize:=ReadLongInt;
  dwFormID:=0;
 end;
end;

function TRiffFiler.ReadForm:TRiffChunk;
begin
 Result:=ReadChunk;
 Result.dwFormID:=ReadLongInt;
end;

function TRiffFiler.ReadChunkNext(const aChunk:TRiffChunk):TRiffChunk;
begin
 SeekChunkNext(aChunk);
 Result:=ReadChunk;
end;

function TRiffFiler.ReadFormNext(const aChunk:TRiffChunk):TRiffChunk;
begin
 SeekChunkNext(aChunk);
 Result:=ReadForm;
end;

function TRiffFiler.CreateChunk(aSign:LongInt):TRiffChunk;
begin
 Result:=RiffChunk(aSign,0,0,Position);
 WriteLongInt(Result.dwSign);
 WriteLongInt(Result.dwSize);
end;

function TRiffFiler.CreateForm(aSign,aFormID:LongInt):TRiffChunk;
begin
 Result:=RiffChunk(aSign,4,aFormID,Position);
 WriteLongInt(Result.dwSign);
 WriteLongInt(Result.dwSize);
 WriteLongInt(Result.dwFormID);
end;

procedure TRiffFiler.FlushChunk(var aChunk:TRiffChunk);
var Pos:LongInt;
begin
 Pos:=Position;
 SeekChunk(aChunk,4);
 aChunk.dwSize:=Pos-aChunk.dwOffset-8;
 WriteLongInt(aChunk.dwSize);
 Position:=Pos;
end;

procedure TRiffFiler.Flush;
var Pos:LongInt;
begin
 if Ok and Modified then begin
  Pos:=Position;
  Position:=Size;
  FlushChunk(myRootForm);
  Position:=Pos;
  Modified:=false;
 end;
end;

procedure TRiffFiler.ForEach(Action    : TRiffFilerForEachAction;
                       const StartForm : TRiffChunk;
                             Custom    : Pointer);
var Terminate:Boolean; aChunk:TRiffChunk; Pos:LongInt;
begin
 if Ok and Assigned(Action) then begin
  Pos:=Position;
  SeekFormData(StartForm);
  Terminate:=false;
  while true do begin
   if (Position<RiffChunkLimit(StartForm)-12) then aChunk:=ReadForm  else
   if (Position<RiffChunkLimit(StartForm)- 8) then aChunk:=ReadChunk else break;
   if (aChunk.dwSize<0) then break;
   if (RiffChunkLimit(aChunk)>RiffChunkLimit(StartForm)) then Break;
   SeekChunkData(aChunk);
   Action(Self,aChunk,Terminate,Custom);
   if Terminate then break;
   SeekChunkNext(aChunk);
  end;
  Position:=Pos;
 end;
end;

procedure TRiffFiler.Validate;
begin
 if Ok then begin
  SeekChunkNext(RootForm);
  if Size>Position then Size:=Position;
  Flush;
 end;
end;

 {
 *******************************************************************************
 Construction / destruction
 *******************************************************************************
 }
function  NewRiffFiler(const aFileName:LongString; aFileMode:Word; aFormID:LongInt):TRiffFiler;
begin
 Result:=nil;
 case aFileMode of
  fmOpenRead      : if not FileExists(aFileName) then Exit;
  fmOpenReadWrite : if not FileExists(aFileName) then aFileMode:=fmCreate;
  fmCreate        : ;
  else Exit;
 end;
 try
  Result:=TRiffFiler.Create(aFileName, aFileMode, aFormID);
  if not Result.Ok then Kill(Result);
 except
  on E:Exception do BugReport(E,nil,'NewRiffFiler');
 end;
end;

procedure Kill(var TheObject:TRiffFiler); overload;
begin
 try
  FreeAndNil(TheObject);
 except
  on E:Exception do BugReport(E,nil,'Kill');
 end; 
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_riff;
begin
end;

procedure Free_crw_riff;
begin
end;

initialization

 Init_crw_riff;

finalization

 Free_crw_riff;

end.

//////////////
// END OF FILE
//////////////

