unit OniDataClass;
interface
uses Data, Classes, SysUtils, StrUtils,
  Dialogs, ABSDecUtil, ABSMain, DB, Windows;

type
  TOniData = class
  private
    FFileName:  String;
    FLevelInfo: TLevelInfo;
    FBackend:   Integer;
    Fos_mac:    Boolean;
  protected
  public
    property FileName: String Read FFileName Write FFileName;
    property Backend: Integer Read FBackend Write FBackend;
    property OSisMac: Boolean Read Fos_mac Write Fos_mac;
    property LevelInfo: TLevelinfo Read FLevelInfo Write FLevelInfo;

    constructor Create(filename: String; var Result: Boolean); virtual; abstract;
    procedure Close; virtual; abstract;

    function GetFileInfo(fileid: Integer): TFileInfo; virtual;
    function GetFilesList(ext: String; pattern: String;
      NoEmptyFiles: Boolean; sort: TSortType): TStringArray; virtual; abstract;
    function GetFilesCount: LongWord; virtual; abstract;
    function GetExtensionsList: TStringArray; virtual; abstract;
    function GetExtendedExtensionsList: TExtensionsMap; virtual; abstract;
    function ExtractFileID(Name: String): Integer;
    function GetFileIDByName(Name: String): Integer;

    function LoadDatFile(fileid: LongWord): Tdata; virtual; abstract;
    procedure UpdateDatFile(fileid: LongWord; Data: Tdata); virtual; abstract;
    procedure LoadDatFilePart(fileid, offset, size: LongWord; target: Pointer);
      virtual; abstract;
    procedure UpdateDatFilePart(fileid, offset, size: LongWord; target: Pointer);
      virtual; abstract;

    function GetRawList(fileid: LongWord): TRawList; virtual; abstract;
    function GetRawInfo(fileid, dat_offset: LongWord): TRawInfo;
    procedure LoadRawFile(fileid, dat_offset: LongWord; target: Pointer);
      virtual; abstract;
    procedure UpdateRawFile(fileid, dat_offset: LongWord; size: LongWord; target: Pointer);
      virtual; abstract;
    procedure LoadRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); virtual; abstract;
    procedure UpdateRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); virtual; abstract;
    function AppendRawFile(loc_sep: Boolean; size: LongWord; target: Pointer): LongWord;
      virtual; abstract;//Returns new Address
  published
  end;

  TOniDataDat = class(TOniData)
  private
    Fdat_file:     TFileStream;
    Fraw_file:     TFileStream;
    Fsep_file:     TFileStream;
    Fdat_header:   THeader;
    Fdat_filesmap: TFilesMap;
    Fdat_files:    TFiles;
    Fdat_namedfilesmap: TNamedFilesMap;
    Fdat_extensionsmap: TExtensionsMap;
    FUnloadWhenUnused: Boolean;
    FDatOpened:    Boolean;
    FRawOpened:    Boolean;
    FSepOpened:    Boolean;
  protected
  public
    property UnloadWhenUnused: Boolean Read FUnloadWhenUnused Write FUnloadWhenUnused;

    constructor Create(DatFilename: String; var Result: Boolean); override;
    procedure Close; override;

    function GetFileInfo(fileid: Integer): TFileInfo; override;
    function GetFilesList(ext: String; pattern: String;
      NoEmptyFiles: Boolean; sort: TSortType): TStringArray; override;
    function GetFilesCount: LongWord; override;
    function GetExtensionsList: TStringArray; override;
    function GetExtendedExtensionsList: TExtensionsMap; override;

    function LoadDatFile(fileid: LongWord): Tdata; override;
    procedure UpdateDatFile(fileid: LongWord; Data: Tdata); override;
    procedure LoadDatFilePart(fileid, offset, size: LongWord; target: Pointer); override;
    procedure UpdateDatFilePart(fileid, offset, size: LongWord; target: Pointer); override;

    procedure LoadRawOffset(loc_sep: Boolean; raw_addr, size: LongWord; target: Pointer);
    function GetRawList(fileid: LongWord): TRawList; override;
    procedure LoadRawFile(fileid, dat_offset: LongWord; target: Pointer); override;
    procedure UpdateRawFile(fileid, dat_offset: LongWord; size: LongWord;
      target: Pointer); override;
    procedure LoadRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); override;
    procedure UpdateRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); override;
    function AppendRawFile(loc_sep: Boolean; size: LongWord; target: Pointer): LongWord;
      override;//Returns new Address
  published
  end;

  TOniDataADB = class(TOniData)
  private
    FDatabase: TABSDatabase;
    FQuery:    TABSQuery;
    Fdat_files:    TFiles;
    Fdat_extensionsmap: TExtensionsMap;
  protected
  public
    constructor Create(OLDBFilename: String; var Result: Boolean); override;
    procedure Close; override;

    procedure UpdateListCache;
    //      function GetDatLinks(srcid:LongWord):TDatLinks;
    function GetFileInfo(fileid: Integer): TFileInfo; override;
    function GetFilesList(ext: String; pattern: String;
      NoEmptyFiles: Boolean; sort: TSortType): TStringArray; override;
    function GetFilesCount: LongWord; override;
    function GetExtensionsList: TStringArray; override;
    function GetExtendedExtensionsList: TExtensionsMap; override;
    function GetNamedFilesMap: TNamedFilesMap;

    function LoadDatFile(fileid: LongWord): Tdata; override;
    procedure UpdateDatFile(fileid: LongWord; Data: Tdata); override;
    procedure LoadDatFilePart(fileid, offset, size: LongWord; target: Pointer); override;
    procedure UpdateDatFilePart(fileid, offset, size: LongWord; target: Pointer); override;

    function GetRawList(fileid: LongWord): TRawList; override;
    procedure LoadRawFile(fileid, dat_offset: LongWord; target: Pointer); override;
    procedure UpdateRawFile(fileid, dat_offset: LongWord; size: LongWord;
      target: Pointer); override;
    procedure LoadRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); override;
    procedure UpdateRawFilePart(fileid, dat_offset: LongWord;
      offset, size: LongWord; target: Pointer); override;
  published
  end;



const
  ODB_None = -1;
  ODB_Dat  = 0;
  ODB_ADB  = 1;

var
  DataConnections: array of TOniData;

function CreateDataConnection(filename: String; backend: Integer): TOniData;
function ConnectionExists(filename: String): TOniData;
procedure CloseDataConnection(connection: TOniData);
function GetEmptyFileInfo: TFileInfo;





implementation
uses DataStructures, Functions;



(*
  Implementation of  TOniData
*)

function TOniData.GetFileIDByName(Name: String): Integer;
var
  files: TStringArray;
  i:     Integer;
begin
  Result := -1;
  files  := Self.GetFilesList('', Name, False, stIDAsc);
  if Length(files) > 0 then
    for i := 0 to High(files) do
      if Pos(Name, files[i]) = Pos('-', files[i]) + 1 then
      begin
        //        if MidStr(files[i],Pos('-',files[i])+1,Length(files[i])-Pos('-',files[i])-5)=name then begin
        Result := Self.ExtractFileID(files[i]);
        Break;
      end;
end;




function TOniData.ExtractFileID(Name: String): Integer;
begin
  if Name[5] = '-' then
    Result := HexToLong(MidStr(Name, 1, 4))
  else
    Result := StrToInt(MidStr(Name, 1, 5));
end;




function TOniData.GetRawInfo(fileid, dat_offset: LongWord): TRawInfo;
var
  i: LongWord;
  raw_list: TRawList;
begin
  raw_list      := Self.GetRawList(fileid);
  Result.src_id := 0;
  Result.src_offset := 0;
  Result.raw_addr := 0;
  Result.raw_size := 0;
  if Length(raw_list) > 0 then
  begin
    for i := 0 to High(raw_list) do
    begin
      if raw_list[i].src_offset = dat_offset then
      begin
        Result.src_id     := fileid;
        Result.src_offset := raw_list[i].src_offset;
        Result.raw_addr   := raw_list[i].raw_addr;
        Result.raw_size   := raw_list[i].raw_size;
        Result.loc_sep    := raw_list[i].loc_sep;
        Break;
      end;
    end;
  end;
end;



function TOniData.GetFileInfo(fileid: Integer): TFileInfo;
begin
  Result.ID := -1;
  Result.FileName := '';
  Result.FileNameHex := '';
  Result.Extension := '';
  Result.Name := '';
  Result.Size := 0;
  Result.FileType := 0;
  Result.DatAddr := 0;
  Result.opened := False;
end;




(*
================================================================================
                      Implementation of  TOniDataDat
*)

constructor TOniDataDat.Create(DatFilename: String; var Result: Boolean);
const
  header_ident1_pc: array[0..$13] of Byte =
    ($1F, $27, $DC, $33, $DF, $BC, $03, $00, $31, $33, $52, $56, $40, $00,
    $14, $00, $10, $00, $08, $00);
  header_ident1_mac: array[0..$13] of Byte =
    ($61, $30, $C1, $23, $DF, $BC, $03, $00, $31, $33, $52, $56, $40, $00,
    $14, $00, $10, $00, $08, $00);
  header_ident1_macbeta: array[0..$13] of Byte =
    ($81, $11, $8D, $23, $DF, $BC, $03, $00, $31, $33, $52, $56, $40, $00,
    $14, $00, $10, $00, $08, $00);
  header_ident2: array[0..$F] of Byte =
    ($99, $CF, $40, $00, $90, $4F, $63, $00, $F4, $55, $5F, $00, $90, $4F, $63, $00);
var
  i: LongWord;
  header_pc, header_mac: Boolean;
begin
  FUnloadWhenUnused := True;
  FDatOpened := False;
  FRawOpened := False;
  if not FileExists(DatFilename) then
  begin
    ShowMessage('File doesn''t exist!!!');
    Result := False;
    Exit;
  end;
  FFileName := DatFilename;
  Fdat_file := TFileStream.Create(FFileName, fmOpenRead);
  Fdat_file.Read(Fdat_header, SizeOf(Fdat_header));
  header_pc  := True;
  header_mac := True;
  for i := 0 to High(Fdat_header.Ident) do
  begin
    FLevelInfo.Ident[i] := Fdat_header.Ident[i];
    if Fdat_header.Ident[i] <> header_ident1_pc[i] then
      header_pc := False;
    if Fdat_header.Ident[i] <> header_ident1_mac[i] then
      header_mac := False;
  end;
  if not (header_pc xor header_mac) then
  begin
    Result := False;
    Exit;
  end
  else
  begin
    if (header_pc and not header_mac) then
      Fos_mac := False
    else
      Fos_mac := True;
  end;
  SetLength(Fdat_filesmap, Fdat_header.Files);
  SetLength(Fdat_files, Fdat_header.Files);
  for i := 0 to Fdat_header.Files - 1 do
    Fdat_file.Read(Fdat_filesmap[i], SizeOf(Fdat_filesmap[i]));
  for i := 0 to Fdat_header.Files - 1 do
  begin
    Fdat_files[i].ID := i;
    Fdat_files[i].Extension := Fdat_filesmap[i].Extension;
    Fdat_files[i].Extension := ReverseString(Fdat_files[i].Extension);
    Fdat_files[i].Size      := Fdat_filesmap[i].FileSize;
    Fdat_files[i].FileType  := Fdat_filesmap[i].FileType;
    Fdat_files[i].DatAddr   := Fdat_filesmap[i].DataAddr - 8 + Fdat_header.DataAddr;
    if (Fdat_filesmap[i].FileType and $01) = 0 then
    begin
      Fdat_file.Seek(Fdat_filesmap[i].NameAddr + Fdat_header.NamesAddr, soFromBeginning);
      SetLength(Fdat_files[i].Name, 100);
      Fdat_file.Read(Fdat_files[i].Name[1], 100);
      Fdat_files[i].Name := MidStr(Fdat_files[i].Name, 1 + 4, Pos(
        #0, Fdat_files[i].Name) - 1 - 4);
    end
    else
    begin
      Fdat_files[i].Name := '';
    end;
    Fdat_files[i].FileName    :=
      FormatNumber(i, 5, '0') + '-' + Fdat_files[i].Name + '.' + Fdat_files[i].Extension;
    Fdat_files[i].FileNameHex :=
      IntToHex(i, 4) + '-' + Fdat_files[i].Name + '.' + Fdat_files[i].Extension;
  end;
  Fdat_file.Seek($40 + Fdat_header.Files * $14, soFromBeginning);
  SetLength(Fdat_namedfilesmap, Fdat_header.NamedFiles);
  for i := 0 to Fdat_header.NamedFiles - 1 do
    Fdat_file.Read(Fdat_namedfilesmap[i], SizeOf(Fdat_namedfilesmap[i]));

  Fdat_file.Seek($40 + Fdat_header.Files * $14 + Fdat_header.NamedFiles * $8, soFromBeginning);
  SetLength(Fdat_extensionsmap, Fdat_header.Extensions);
  for i := 0 to Fdat_header.Extensions - 1 do
    Fdat_file.Read(Fdat_extensionsmap[i], SizeOf(Fdat_extensionsmap[i]));

  Fdat_file.Seek(Fdat_files[0].DatAddr + 7, soFromBeginning);
  Fdat_file.Read(FLevelInfo.LevelNumber, 1);
  FLevelInfo.LevelNumber := FLevelInfo.LevelNumber div 2;

  Fdat_file.Free;

  Result   := True;
  FBackend := ODB_Dat;
end;




procedure TOniDataDat.Close;
begin
  if not FUnloadWhenUnused and FDatOpened then
    Fdat_file.Free;
  if not FUnloadWhenUnused and FRawOpened then
    Fraw_file.Free;
  if not FUnloadWhenUnused and FSepOpened then
    Fsep_file.Free;
  Self.Free;
end;




function TOniDataDat.GetFileInfo(fileid: Integer): TFileInfo;
begin
  if fileid = -1 then
  begin
    Result := inherited GetFileInfo(fileid);
    Exit;
  end;
  if fileid < Self.GetFilesCount then
    Result    := Fdat_files[fileid]
  else
    Result.ID := -1;
end;




function TOniDataDat.GetFilesList(ext: String; pattern: String;
  NoEmptyFiles: Boolean; sort: TSortType): TStringArray;
var
  i: LongWord;
  list: TStringList;
  id, name, extension: String;
  fields: TStrings;

  procedure getfields;
  begin
     fields.CommaText := StringReplace(AnsiQuotedStr(list.Strings[i], '"'), ';', '","', [rfReplaceAll]);
    if sort in [stIDAsc, stIDDesc] then
    begin
      id := fields.Strings[0];
      name := fields.Strings[1];
      extension := fields.Strings[2];
    end;
    if sort in [stNameAsc, stNameDesc] then
    begin
      id := fields.Strings[1];
      name := fields.Strings[0];
      extension := fields.Strings[2];
    end;
    if sort in [stExtAsc, stExtDesc] then
    begin
      id := fields.Strings[1];
      name := fields.Strings[2];
      extension := fields.Strings[0];
    end;
  end;

begin
  list := TStringList.Create;
  list.Sorted := True;
  for i := 0 to Fdat_header.Files - 1 do
  begin
    if ((Length(ext) = 0) or (Pos(Fdat_files[i].Extension, ext) > 0)) and
      ((Length(pattern) = 0) or
      (Pos(UpperCase(pattern), UpperCase(Fdat_files[i].Name)) > 0)) then
    begin
      if (NoEmptyFiles = False) or ((Fdat_files[i].FileType and $02) = 0) then
      begin
        if AppSettings.FilenumbersAsHex then
          id := IntToHex(Fdat_files[i].ID, 4)
        else
          id := FormatNumber(Fdat_files[i].ID, 5, '0');
        name := Fdat_files[i].Name;
        extension := Fdat_files[i].Extension;

        case sort of
          stIDAsc, stIDDesc:     list.Add(id + ';' + name + ';' + extension);
          stNameAsc, stNameDesc: list.Add(name + ';' + id + ';' + extension);
          stExtAsc, stExtDesc:   list.Add(extension + ';' + id + ';' + name);
        end;
      end;
    end;
  end;
  SetLength(Result, list.Count);
  if Length(Result) > 0 then
  begin
    fields := TStringList.Create;
    if sort in [stIDAsc, stNameAsc, stExtAsc] then
      for i := 0 to list.Count - 1 do
      begin
        getfields;
        Result[i] := id + '-' + name + '.' + extension;
      end
    else
      for i := list.Count - 1 downto 0 do
      begin
        getfields;
        Result[list.Count - i - 1] := id + '-' + name + '.' + extension;
      end;
    fields.Free;
  end;
  list.Free;
end;




function TOniDataDat.GetFilesCount: LongWord;
begin
  Result := Fdat_header.Files;
end;




function TOniDataDat.GetExtensionsList: TStringArray;
var
  i: LongWord;
begin
  SetLength(Result, Fdat_header.Extensions);
  for i := 0 to Fdat_header.Extensions - 1 do
  begin
    with Fdat_extensionsmap[i] do
    begin
      Result[i] := Extension[3] + Extension[2] + Extension[1] + Extension[0] +
        ' (' + IntToStr(ExtCount) + ')';
    end;
  end;
end;




function TOniDataDat.GetExtendedExtensionsList: TExtensionsMap;
var
  i: LongWord;
begin
  SetLength(Result, Fdat_header.Extensions);
  for i := 0 to Fdat_header.Extensions - 1 do
  begin
    Result[i] := Fdat_extensionsmap[i];
  end;
end;




function TOniDataDat.LoadDatFile(fileid: LongWord): Tdata;
begin
  if fileid < Self.GetFilesCount then
  begin
    if FUnloadWhenUnused or not FDatOpened then
      Fdat_file := TFileStream.Create(FFileName, fmOpenReadWrite);
    Fdat_file.Seek(Fdat_files[fileid].DatAddr, soFromBeginning);
    SetLength(Result, Fdat_files[fileid].Size);
    Fdat_file.Read(Result[0], Fdat_files[fileid].Size);
    if UnloadWhenUnused then
      Fdat_file.Free
    else
      FDatOpened := True;
  end;
end;




procedure TOniDataDat.UpdateDatFile(fileid: LongWord; Data: Tdata);
begin
  if fileid < Self.GetFilesCount then
  begin
    if FUnloadWhenUnused or not FDatOpened then
      Fdat_file := TFileStream.Create(FFileName, fmOpenReadWrite);
    Fdat_file.Seek(Fdat_files[fileid].DatAddr, soFromBeginning);
    Fdat_file.Write(Data[0], Length(Data));
    if UnloadWhenUnused then
      Fdat_file.Free
    else
      FDatOpened := True;
  end;
end;




procedure TOniDataDat.LoadDatFilePart(fileid, offset, size: LongWord; target: Pointer);
begin
  if fileid < Self.GetFilesCount then
  begin
    if FUnloadWhenUnused or not FDatOpened then
      Fdat_file := TFileStream.Create(FFileName, fmOpenReadWrite);
    Fdat_file.Seek(Fdat_files[fileid].DatAddr + offset, soFromBeginning);
    Fdat_file.Read(target^, size);
    if UnloadWhenUnused then
      Fdat_file.Free
    else
      FDatOpened := True;
  end;
end;




procedure TOniDataDat.UpdateDatFilePart(fileid, offset, size: LongWord; target: Pointer);
begin
  if fileid < Self.GetFilesCount then
  begin
    if FUnloadWhenUnused or not FDatOpened then
      Fdat_file := TFileStream.Create(FFileName, fmOpenReadWrite);
    Fdat_file.Seek(Fdat_files[fileid].DatAddr + offset, soFromBeginning);
    Fdat_file.Write(target^, size);
    if UnloadWhenUnused then
      Fdat_file.Free
    else
      FDatOpened := True;
  end;
end;




function TOniDataDat.GetRawList(fileid: LongWord): TRawList;
var
  i: LongWord;
begin
  SetLength(Result, 0);
  for i := 0 to High(RawListHandlers) do
    if UpperCase(RawListHandlers[i].Ext) = UpperCase(Fdat_files[fileid].extension) then
      if RawListHandlers[i].needed then
      begin
        Result := RawListHandlers[i].Handler(Self, fileid);
        Break;
      end
      else
        Break;
end;




procedure TOniDataDat.LoadRawOffset(loc_sep: Boolean; raw_addr, size: LongWord;
  target: Pointer);
begin
  if not loc_sep then
  begin
    if FUnloadWhenUnused or not FRawOpened then
      Fraw_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.raw'),
        fmOpenReadWrite);
    if raw_addr <= Fraw_file.Size then
    begin
      Fraw_file.Seek(raw_addr, soFromBeginning);
      Fraw_file.Read(target^, size);
    end;
    if UnloadWhenUnused then
      Fraw_file.Free
    else
      FRawOpened := True;
  end
  else
  begin
    if FUnloadWhenUnused or not FSepOpened then
      Fsep_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.sep'),
        fmOpenReadWrite);
    if raw_addr <= Fsep_file.Size then
    begin
      Fsep_file.Seek(raw_addr, soFromBeginning);
      Fsep_file.Read(target^, size);
    end;
    if UnloadWhenUnused then
      Fsep_file.Free
    else
      FSepOpened := True;
  end;
end;




procedure TOniDataDat.LoadRawFile(fileid, dat_offset: LongWord; target: Pointer);
var
  raw_info: TRawInfo;
begin
  if fileid < Self.GetFilesCount then
  begin
    raw_info := Self.GetRawInfo(fileid, dat_offset);
    if not raw_info.loc_sep then
    begin
      if FUnloadWhenUnused or not FRawOpened then
        Fraw_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.raw'),
          fmOpenReadWrite);
      Fraw_file.Seek(raw_info.raw_addr, soFromBeginning);
      Fraw_file.Read(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fraw_file.Free
      else
        FRawOpened := True;
    end
    else
    begin
      if FUnloadWhenUnused or not FSepOpened then
        Fsep_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.sep'),
          fmOpenReadWrite);
      Fsep_file.Seek(raw_info.raw_addr, soFromBeginning);
      Fsep_file.Read(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fsep_file.Free
      else
        FSepOpened := True;
    end;
  end;
end;




procedure TOniDataDat.UpdateRawFile(fileid, dat_offset: LongWord;
  size: LongWord; target: Pointer);
var
  raw_info: TRawInfo;
begin
  if fileid < Self.GetFilesCount then
  begin
    raw_info := Self.GetRawInfo(fileid, dat_offset);
    if not raw_info.loc_sep then
    begin
      if FUnloadWhenUnused or not FRawOpened then
        Fraw_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.raw'),
          fmOpenReadWrite);
      Fraw_file.Seek(raw_info.raw_addr, soFromBeginning);
      Fraw_file.Write(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fraw_file.Free
      else
        FRawOpened := True;
    end
    else
    begin
      if FUnloadWhenUnused or not FSepOpened then
        Fsep_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.sep'),
          fmOpenReadWrite);
      Fsep_file.Seek(raw_info.raw_addr, soFromBeginning);
      Fsep_file.Write(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fsep_file.Free
      else
        FSepOpened := True;
    end;
  end;
end;




procedure TOniDataDat.LoadRawFilePart(fileid, dat_offset: LongWord;
  offset, size: LongWord; target: Pointer);
var
  raw_info: TRawInfo;
  Data:     Tdata;
  mem:      TMemoryStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    raw_info := Self.GetRawInfo(fileid, dat_offset);
    SetLength(Data, raw_info.raw_size);
    Self.LoadRawFile(fileid, dat_offset, @Data[0]);
    mem := TMemoryStream.Create;
    mem.Write(Data[offset], size);
    mem.Read(target^, size);
    mem.Free;
  end;
end;




procedure TOniDataDat.UpdateRawFilePart(fileid, dat_offset: LongWord;
  offset, size: LongWord; target: Pointer);
var
  raw_info: TRawInfo;
begin
  if fileid < Self.GetFilesCount then
  begin
    raw_info := Self.GetRawInfo(fileid, dat_offset);
    if not raw_info.loc_sep then
    begin
      if FUnloadWhenUnused or not FRawOpened then
        Fraw_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.raw'),
          fmOpenReadWrite);
      Fraw_file.Seek(raw_info.raw_addr + offset, soFromBeginning);
      Fraw_file.Write(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fraw_file.Free
      else
        FRawOpened := True;
    end
    else
    begin
      if FUnloadWhenUnused or not FSepOpened then
        Fsep_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.sep'),
          fmOpenReadWrite);
      Fsep_file.Seek(raw_info.raw_addr + offset, soFromBeginning);
      Fsep_file.Write(target^, raw_info.raw_size);
      if UnloadWhenUnused then
        Fsep_file.Free
      else
        FSepOpened := True;
    end;
  end;
end;




function TOniDataDat.AppendRawFile(loc_sep: Boolean; size: LongWord;
  target: Pointer): LongWord; //Returns new Address
begin
  if not loc_sep then
  begin
    if FUnloadWhenUnused or not FRawOpened then
      Fraw_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.raw'),
        fmOpenReadWrite);
    Result := Fraw_file.Size;
    Fraw_file.Seek(0, soFromEnd);
    Fraw_file.Write(target^, size);
    if UnloadWhenUnused then
      Fraw_file.Free
    else
      FRawOpened := True;
  end
  else
  begin
    if FUnloadWhenUnused or not FSepOpened then
      Fsep_file := TFileStream.Create(AnsiReplaceStr(FFileName, '.dat', '.sep'),
        fmOpenReadWrite);
    Result := Fsep_file.Size;
    Fsep_file.Seek(0, soFromEnd);
    Fsep_file.Write(target^, size);
    if UnloadWhenUnused then
      Fsep_file.Free
    else
      FSepOpened := True;
  end;
end;











(*
================================================================================
                     Implementation of  TOniDataADB
*)

constructor TOniDataADB.Create(OLDBFilename: String; var Result: Boolean);
var
  i, j:  Byte;
  temps: String;
begin
  if not FileExists(OLDBFilename) then
  begin
    ShowMessage('File doesn''t exist!!!');
    Result := False;
    Exit;
  end;
  FFileName := OLDBFilename;
  FDatabase := TABSDatabase.Create(nil);
  FDatabase.DatabaseName := 'OLDBcon';
  FDatabase.DatabaseFileName := OLDBFilename;
  FDatabase.Open;
  FQuery := TABSQuery.Create(FDatabase);
  FQuery.DatabaseName := 'OLDBcon';
  FQuery.SQL.Text := 'SELECT [name],[value] FROM globals ORDER BY [name] ASC';
  FQuery.Open;
  FQuery.First;
  repeat
    if FQuery.FieldByName('name').AsString = 'dbversion' then
    begin
      if FQuery.FieldByName('value').AsString <> DBversion then
      begin
        ShowMessage('Database-file ' + #13 + #10 +
          '"' + OLDBFilename + '"' + #13 + #10 +
          'has wrong version. (Required: ' + DBversion + '; found: ' +
          FQuery.FieldByName('value').AsString + ')');
        FQuery.Close;
        Result := False;
        Exit;
      end;
    end;
    if FQuery.FieldByName('name').AsString = 'lvl' then
    begin
      FLevelInfo.LevelNumber := StrToInt(FQuery.FieldByName('value').AsString);
    end;
    if FQuery.FieldByName('name').AsString = 'ident' then
    begin
      temps := FQuery.FieldByName('value').AsString;
      for i := 0 to High(FLevelInfo.Ident) do
      begin
        j := i * 2 + 1;
        case temps[j] of
          '0'..'9':
            FLevelInfo.Ident[i] := Ord(temps[j]) - 48;
          'A'..'F':
            FLevelInfo.Ident[i] := Ord(temps[j]) - 55;
        end;
        FLevelInfo.Ident[i] := FLevelInfo.Ident[i] * 16;
        case temps[j + 1] of
          '0'..'9':
            FLevelInfo.Ident[i] := FLevelInfo.Ident[i] + Ord(temps[j + 1]) - 48;
          'A'..'F':
            FLevelInfo.Ident[i] := FLevelInfo.Ident[i] + Ord(temps[j + 1]) - 55;
        end;
      end;
    end;
    if FQuery.FieldByName('name').AsString = 'ident' then
    begin
      temps   := FQuery.FieldByName('value').AsString;
      Fos_mac := temps = 'MAC';
    end;
    FQuery.Next;
  until FQuery.EOF;
  FQuery.Close;

  UpdateListCache;

  Result   := True;
  FBackend := ODB_ADB;
end;




procedure TOniDataADB.Close;
begin
  FDatabase.Close;
  FDatabase.Free;
  Self.Free;
end;



procedure TOniDataADB.UpdateListCache;
var
  i:     LongWord;
  temps: String;
begin
  FQuery.SQL.Text := 'SELECT id,name,extension,[size],contenttype FROM datfiles ORDER BY id ASC;';
  FQuery.Open;
  if FQuery.RecordCount > 0 then
  begin
    FQuery.First;
    SetLength(Fdat_files, FQuery.RecordCount);
    i := 0;
    repeat
      Fdat_files[i].ID := FQuery.FieldByName('id').AsInteger;
      Fdat_files[i].Name := FQuery.FieldByName('name').AsString;
      Fdat_files[i].Extension := FQuery.FieldByName('extension').AsString;
      Fdat_files[i].FileName := FormatNumber(Fdat_files[i].ID, 5, '0') + '-' +
          Fdat_files[i].Name + '.' + Fdat_files[0].Extension;
      Fdat_files[i].FileNameHex := IntToHex(Fdat_files[i].ID, 4) + '-' +
          Fdat_files[i].Name + '.' + Fdat_files[0].Extension;
      Fdat_files[i].Size := FQuery.FieldByName('size').AsInteger;
      Fdat_files[i].FileType := HexToLong(FQuery.FieldByName('contenttype').AsString);
      Fdat_files[i].DatAddr := 0;
      Fdat_files[i].opened := False;
      Inc(i);
      FQuery.Next;
    until FQuery.EOF;
  end;
  FQuery.Close;

  SetLength(Fdat_extensionsmap, 0);
  FQuery.SQL.Text :=
    'SELECT extension,count(extension) AS x FROM datfiles GROUP BY extension ORDER BY extension ASC;';
  FQuery.Open;
  if FQuery.RecordCount > 0 then
  begin
    SetLength(Fdat_extensionsmap, FQuery.RecordCount);
    i := 0;
    repeat
      temps := FQuery.FieldByName('extension').AsString[1];
      Fdat_extensionsmap[i].Extension[3] := temps[1];
      Fdat_extensionsmap[i].Extension[2] := temps[2];
      Fdat_extensionsmap[i].Extension[1] := temps[3];
      Fdat_extensionsmap[i].Extension[0] := temps[4];
      Fdat_extensionsmap[i].ExtCount := FQuery.FieldByName('x').AsInteger;
      Inc(i);
      FQuery.Next;
    until FQuery.EOF;
  end;
  FQuery.Close;
end;


function TOniDataADB.GetFileInfo(fileid: Integer): TFileInfo;
var
  i: Integer;
begin
  if fileid = -1 then
  begin
    Result := inherited GetFileInfo(fileid);
    Exit;
  end;
  if fileid < Self.GetFilesCount then
  begin
    for i := 0 to High(Fdat_files) do
      if Fdat_files[i].ID = fileid then
        Break;
    if i < Length(Fdat_files) then
      Result := Fdat_files[i]
    else
      Result.ID := -1;
  end
  else
  begin
    Result.ID := -1;
  end;
end;




function TOniDataADB.GetFilesList(ext: String; pattern: String;
  NoEmptyFiles: Boolean; sort: TSortType): TStringArray;
var
  i: LongWord;
  list: TStringList;
  id, name, extension: String;
  fields: TStrings;

  procedure getfields;
  begin
     fields.CommaText := StringReplace(AnsiQuotedStr(list.Strings[i], '"'), ';', '","', [rfReplaceAll]);
    if sort in [stIDAsc, stIDDesc] then
    begin
      id := fields.Strings[0];
      name := fields.Strings[1];
      extension := fields.Strings[2];
    end;
    if sort in [stNameAsc, stNameDesc] then
    begin
      id := fields.Strings[1];
      name := fields.Strings[0];
      extension := fields.Strings[2];
    end;
    if sort in [stExtAsc, stExtDesc] then
    begin
      id := fields.Strings[1];
      name := fields.Strings[2];
      extension := fields.Strings[0];
    end;
  end;

begin
  list := TStringList.Create;
  list.Sorted := True;
  for i := 0 to High(Fdat_files) do
  begin
    if ((Length(ext) = 0) or (Pos(Fdat_files[i].Extension, ext) > 0)) and
      ((Length(pattern) = 0) or
      (Pos(UpperCase(pattern), UpperCase(Fdat_files[i].Name)) > 0)) then
    begin
      if (NoEmptyFiles = False) or ((Fdat_files[i].FileType and $02) = 0) then
      begin
        if AppSettings.FilenumbersAsHex then
          id := IntToHex(Fdat_files[i].ID, 4)
        else
          id := FormatNumber(Fdat_files[i].ID, 5, '0');
        name := Fdat_files[i].Name;
        extension := Fdat_files[i].Extension;

        case sort of
          stIDAsc, stIDDesc:     list.Add(id + ';' + name + ';' + extension);
          stNameAsc, stNameDesc: list.Add(name + ';' + id + ';' + extension);
          stExtAsc, stExtDesc:   list.Add(extension + ';' + id + ';' + name);
        end;
      end;
    end;
  end;
  SetLength(Result, list.Count);
  fields := TStringList.Create;
  if sort in [stIDAsc, stNameAsc, stExtAsc] then
    for i := 0 to list.Count - 1 do
    begin
      getfields;
      Result[i] := id + '-' + name + '.' + extension;
    end
  else
    for i := list.Count - 1 downto 0 do
    begin
      getfields;
      Result[list.Count - i - 1] := id + '-' + name + '.' + extension;
    end;
  list.Free;
  fields.Free;
end;




function TOniDataADB.GetFilesCount: LongWord;
begin
  Result := Length(Fdat_files);
end;




function TOniDataADB.GetExtensionsList: TStringArray;
var
  i: LongWord;
begin
  SetLength(Result, Length(Fdat_extensionsmap));
  for i := 0 to High(Result) do
  begin
    with Fdat_extensionsmap[i] do
    begin
      Result[i] := Extension[3] + Extension[2] + Extension[1] + Extension[0] +
        ' (' + IntToStr(ExtCount) + ')';
    end;
  end;
end;




function TOniDataADB.GetExtendedExtensionsList: TExtensionsMap;
var
  i, j:  LongWord;
  temps: String;
  Data:  Tdata;
begin
  SetLength(Result, 0);
  FQuery.SQL.Text := 'SELECT ext,ident FROM extlist ORDER BY ext ASC;';
  FQuery.Open;
  if FQuery.RecordCount > 0 then
  begin
    SetLength(Result, FQuery.RecordCount);
    i := 0;
    repeat
      temps := FQuery.FieldByName('ext').AsString;
      for j := 0 to 3 do
        Result[i].Extension[j] := temps[4 - j];
      Data := DecodeHexString(FQuery.FieldByName('ident').AsString);
      for j := 0 to 7 do
        Result[i].Ident[j] := Data[j];
      Inc(i);
      FQuery.Next;
    until FQuery.EOF;
  end;
  FQuery.Close;
end;




function TOniDataADB.GetNamedFilesMap: TNamedFilesMap;
var
  i:     LongWord;
  temp:  Integer;
  temps: String;
  temparray: array of record
    id: Integer;
    fullname: String[50];
  end;
begin
  SetLength(temparray, 0);
  FQuery.SQL.Text :=
    'SELECT id,(extension+name) AS xname FROM datfiles WHERE Length(name)>0 ORDER BY extension,name ASC;';
  FQuery.Open;
  if FQuery.RecordCount > 0 then
  begin
    repeat
      temp  := FQuery.FieldByName('id').AsInteger;
      temps := FQuery.FieldByName('xname').AsString;

      SetLength(temparray, Length(temparray) + 1);
      if Length(temparray) > 1 then
      begin
        for i := High(temparray) - 1 downto 0 do
        begin
          if StringSmaller(temps, temparray[i].fullname) then
          begin
            temparray[i + 1] := temparray[i];
            if i = 0 then
            begin
              temparray[i].id := temp;
              temparray[i].fullname := temps;
            end;
          end
          else
          begin
            temparray[i + 1].id := temp;
            temparray[i + 1].fullname := temps;
            Break;
          end;
        end;
      end
      else
      begin
        temparray[0].id := temp;
        temparray[0].fullname := temps;
      end;
      FQuery.Next;
    until FQuery.EOF;
  end;
  FQuery.Close;
  SetLength(Result, Length(temparray));
  for i := 0 to High(temparray) do
  begin
    Result[i].FileNumber := temparray[i].id;
    Result[i].blubb      := 0;
  end;
end;




function TOniDataADB.LoadDatFile(fileid: LongWord): Tdata;
var
  mem: TStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    FQuery.SQL.Text := 'SELECT data FROM datfiles WHERE id=' + IntToStr(fileid) + ';';
    FQuery.Open;
    if FQuery.RecordCount > 0 then
    begin
      mem := FQuery.CreateBlobStream(FQuery.FieldByName('data'), bmRead);
      SetLength(Result, mem.Size);
      mem.Seek(0, soFromBeginning);
      mem.Read(Result[0], mem.Size);
      mem.Free;
    end;
    FQuery.Close;
  end;
end;




procedure TOniDataADB.UpdateDatFile(fileid: LongWord; Data: Tdata);
var
  MimeCoder: TStringFormat_MIME64;
  mem: TMemoryStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    mimecoder := TStringFormat_MIME64.Create;
    mem := TMemoryStream.Create;
    mem.Write(Data[0], Length(Data));
    mem.Seek(0, soFromBeginning);
    FQuery.SQL.Text := 'UPDATE datfiles SET data=MimeToBin("' +
      MimeCoder.StrTo(mem.Memory, mem.Size) + '"), size=' + IntToStr(mem.Size) +
      ' WHERE id=' + IntToStr(fileid) + ';';
    FQuery.ExecSQL;
    mem.Free;
    mimecoder.Free;
  end;
  UpdateListCache;
end;




procedure TOniDataADB.LoadDatFilePart(fileid, offset, size: LongWord; target: Pointer);
var
  mem: TStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    FQuery.SQL.Text := 'SELECT data FROM datfiles WHERE id=' + IntToStr(fileid) + ';';
    FQuery.Open;
    if FQuery.RecordCount > 0 then
    begin
      mem := FQuery.CreateBlobStream(FQuery.FieldByName('data'), bmRead);
      mem.Seek(offset, soFromBeginning);
      mem.Read(target^, size);
      mem.Free;
    end;
    FQuery.Close;
  end;
end;




procedure TOniDataADB.UpdateDatFilePart(fileid, offset, size: LongWord; target: Pointer);
var
  MimeCoder: TStringFormat_MIME64;
  mem:  TMemoryStream;
  Data: Tdata;
begin
  if fileid < Self.GetFilesCount then
  begin
    Data := Self.LoadDatFile(fileid);
    mimecoder := TStringFormat_MIME64.Create;
    mem := TMemoryStream.Create;
    mem.Write(Data[0], Length(Data));
    mem.Seek(offset, soFromBeginning);
    mem.Write(target^, size);
    mem.Seek(0, soFromBeginning);
    FQuery.SQL.Text := 'UPDATE datfiles SET data=MimeToBin("' +
      MimeCoder.StrTo(mem.Memory, mem.Size) + '") WHERE id=' + IntToStr(fileid) + ';';
    FQuery.ExecSQL;
    mem.Free;
    mimecoder.Free;
  end;
end;




function TOniDataADB.GetRawList(fileid: LongWord): TRawList;
var
  i: LongWord;
begin
  SetLength(Result, 0);
  FQuery.SQL.Text := 'SELECT [src_link_offset],[size],[sep] FROM rawmap WHERE [src_id]=' +
    IntToStr(fileid) + ' ORDER BY src_link_offset ASC;';
  FQuery.Open;
  if FQuery.RecordCount > 0 then
  begin
    FQuery.First;
    SetLength(Result, FQuery.RecordCount);
    i := 0;
    repeat
      Result[i].src_id     := fileid;
      Result[i].src_offset := FQuery.FieldByName('src_link_offset').AsInteger;
      Result[i].raw_addr   := 0;
      Result[i].raw_size   := FQuery.FieldByName('size').AsInteger;
      Result[i].loc_sep    := FQuery.FieldByName('sep').AsBoolean;
      Inc(i);
      FQuery.Next;
    until FQuery.EOF;
  end;
  FQuery.Close;
end;




procedure TOniDataADB.LoadRawFile(fileid, dat_offset: LongWord; target: Pointer);
var
  mem: TStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    FQuery.SQL.Text := 'SELECT data FROM rawmap WHERE (src_id=' +
      IntToStr(fileid) + ') AND (src_link_offset=' + IntToStr(dat_offset) + ');';
    FQuery.Open;
    if FQuery.RecordCount > 0 then
    begin
      mem := FQuery.CreateBlobStream(FQuery.FieldByName('data'), bmRead);
      mem.Seek(0, soFromBeginning);
      mem.Read(target^, mem.size);
      mem.Free;
    end;
    FQuery.Close;
  end;
end;




procedure TOniDataADB.UpdateRawFile(fileid, dat_offset: LongWord;
  size: LongWord; target: Pointer);
var
  MimeCoder: TStringFormat_MIME64;
  mem: TMemoryStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    mimecoder := TStringFormat_MIME64.Create;
    mem := TMemoryStream.Create;
    mem.Write(target^, size);
    mem.Seek(0, soFromBeginning);
    FQuery.SQL.Text := 'UPDATE rawmap SET data=MimeToBin("' + MimeCoder.StrTo(
      mem.Memory, mem.Size) + '") WHERE (src_id=' + IntToStr(fileid) +
      ') AND (src_link_offset=' + IntToStr(dat_offset) + ');';
    FQuery.ExecSQL;
    mem.Free;
    mimecoder.Free;
  end;
end;




procedure TOniDataADB.LoadRawFilePart(fileid, dat_offset: LongWord;
  offset, size: LongWord; target: Pointer);
var
  Data: Tdata;
  mem:  TMemoryStream;
begin
  if fileid < Self.GetFilesCount then
  begin
    SetLength(Data, Self.GetRawInfo(fileid, dat_offset).raw_size);
    Self.LoadRawFile(fileid, dat_offset, @Data[0]);
    mem := TMemoryStream.Create;
    mem.Write(Data[offset], size);
    mem.Read(target^, size);
    mem.Free;
  end;
end;




procedure TOniDataADB.UpdateRawFilePart(fileid, dat_offset: LongWord;
  offset, size: LongWord; target: Pointer);
var
  MimeCoder: TStringFormat_MIME64;
  mem:  TMemoryStream;
  Data: Tdata;
begin
  if fileid < Self.GetFilesCount then
  begin
    SetLength(Data, Self.GetRawInfo(fileid, offset).raw_size);
    Self.LoadRawFile(fileid, offset, @Data[0]);
    mimecoder := TStringFormat_MIME64.Create;
    mem := TMemoryStream.Create;
    mem.Write(Data[0], Length(Data));
    mem.Seek(offset, soFromBeginning);
    mem.Write(target^, size);
    mem.Seek(0, soFromBeginning);
    FQuery.SQL.Text := 'UPDATE rawmap SET data=MimeToBin("' + MimeCoder.StrTo(
      mem.Memory, mem.Size) + '") WHERE (src_id=' + IntToStr(fileid) +
      ') AND (src_link_offset=' + IntToStr(dat_offset) + ');';
    FQuery.ExecSQL;
    mem.Free;
    mimecoder.Free;
  end;
end;











function CreateDataConnection(filename: String; backend: Integer): TOniData;
var
  answer: Boolean;
  i: Integer;
begin
  if Length(DataConnections) > 0 then
  begin
    for i := 0 to High(DataConnections) do
    begin
      if ExtractFileName(DataConnections[i].FFileName) = ExtractFileName(filename) then
      begin
        if DataConnections[i].FFileName <> filename then
        begin
          Result := nil;
          ShowMessage('You can not open two files with the same name at a time.');
        end
        else
          Result := DataConnections[i];
        Exit;
      end;
    end;
  end;

  if not FileExists(filename) then
  begin
    ShowMessage('File "' + filename + '" does not exist!');
    Result := nil;
    Exit;
  end;

  SetLength(DataConnections, Length(DataConnections) + 1);
  i := High(DataConnections);
  case backend of
    ODB_Dat:
      DataConnections[i] := TOniDataDat.Create(filename, answer);
    ODB_ADB:
      DataConnections[i] := TOniDataADB.Create(filename, answer);
    else
      ShowMessage('Unknown Backend');
      Result := nil;
      Exit;
  end;

  if answer then
  begin
    Result := DataConnections[i];
//    Result := True;
  end
  else
  begin
    ShowMessage('File not loaded');
    DataConnections[i].Close;
    DataConnections[i].Free;
    DataConnections[i] := nil;
    SetLength(DataConnections, Length(DataConnections) - 1);
    Result := nil;
  end;
end;


function ConnectionExists(filename: String): TOniData;
var
  i: Integer;
begin
  Result := nil;
  if Length(DataConnections) > 0 then
    for i := 0 to High(DataConnections) do
      if DataConnections[i].FFileName = filename then
      begin
        Result := DataConnections[i];
        Exit;
      end;
end;


procedure CloseDataConnection(connection: TOniData);
var
  i: Integer;
  found: Boolean;
begin
  if Assigned(connection) then
  begin
    found := False;
    for i := 0 to High(DataConnections) do
    begin
      if not found then
      begin
        if DataConnections[i] = connection then
        begin
          DataConnections[i].Close;
//          DataConnections[i].Free;
          DataConnections[i] := nil;
          found := True;
        end;
      end
      else
      begin
        DataConnections[i - 1] := DataConnections[i];
      end;
    end;
    if found then
      SetLength(DataConnections, Length(DataConnections) - 1);
  end;
end;


function GetEmptyFileInfo: TFileInfo;
begin
  Result.ID := -1;
  Result.FileName := '';
  Result.FileNameHex := '';
  Result.Extension := '';
  Result.Name := '';
  Result.Size := 0;
  Result.FileType := 0;
  Result.DatAddr := 0;
  Result.opened := False;
end;


end.
