UNIT Unit2_functions;
INTERFACE
USES Classes, Dialogs, SysUtils, StrUtils, Math, SQLiteTable3,
      Unit3_data, Unit4_Exporters;

TYPE
  TExportSet=SET OF (DO_dat,DO_raw,DO_convert,DO_toone);

FUNCTION GetFilesList(ext:String; pattern:String; NoEmptyFiles:Boolean):TStringList;
FUNCTION GetExtensionsList:TStringList;

FUNCTION Decode_Int(buffer:Tdata):LongWord;
FUNCTION Encode_Int(input:LongWord):Tdata;
FUNCTION Decode_Float(buffer:Tdata):Single;
FUNCTION Encode_Float(input:Single):Tdata;

FUNCTION LoadDatInfos(filename:String):Boolean;
PROCEDURE OpenDatabase(FileName:String);

FUNCTION LoadDatFile(fileid:LongWord):Tdata;
PROCEDURE UpdateDatFile(fileid:LongWord; data:Tdata);
FUNCTION LoadDatFilePart(fileid,offset,size:LongWord; target:Pointer):Boolean;
FUNCTION UpdateDatFilePart(fileid,offset,size:LongWord; target:Pointer):Boolean;

FUNCTION LoadRawFile(fileid,datlinkoffset,size:LongWord; target:Pointer):Boolean;
FUNCTION UpdateRawFile(fileid,datlinkoffset,size:LongWord; target:Pointer):Boolean;

FUNCTION ExportFile(fileid:LongWord; filename:String; settings:TExportSet; path:String):Integer;

FUNCTION FormatNumber(value:LongWord; width:Byte; leadingzeros:Char):String;
FUNCTION FormatFileSize(size:LongWord):String;
FUNCTION CreateHexString(data:Tdata; HexOnly:Boolean):String;
FUNCTION GetWinFileName(name:String):String;
FUNCTION GetExtractPath:String;


IMPLEMENTATION

TYPE
  TValueSwitcher=Record
    CASE IsFloat: Boolean OF
      True: (ValueFloat:Single);
      False: (ValueInt:LongWord);
  END;

FUNCTION Decode_Int(buffer:Tdata):LongWord;
  BEGIN
    Result:=buffer[0]+buffer[1]*256+buffer[2]*256*256+buffer[3]*256*256*256;
  END;
FUNCTION Encode_Int(input:LongWord):Tdata;
  BEGIN
    Result[0]:=input MOD 256;
    input:=input DIV 256;
    Result[1]:=input MOD 256;
    input:=input DIV 256;
    Result[2]:=input MOD 256;
    input:=input DIV 256;
    Result[3]:=input MOD 256;
  END;
FUNCTION Decode_Float(buffer:Tdata):Single;
  VAR _valueswitcher:TValueSwitcher;
  BEGIN
    _valueswitcher.ValueInt:=Decode_Int(buffer);
    Result:=_valueswitcher.ValueFloat;
  END;
FUNCTION Encode_Float(input:Single):Tdata;
  VAR _valueswitcher:TValueSwitcher;
  BEGIN
    _valueswitcher.ValueFloat:=input;
    Result:=Encode_Int(_valueswitcher.ValueInt);
  END;


FUNCTION GetFilesList(ext:String; pattern:String; NoEmptyFiles:Boolean):TStringList;
  VAR
    i:LongWord;
    where:String;
    Tbl:TSQLiteTable;
  BEGIN
    SetLength(Result,0);
    IF opened_state=opened_dat THEN BEGIN
      FOR i:=0 TO dat_header.Files-1 DO BEGIN
        IF ( (Length(ext)=0) OR (dat_files[i].Extension=ext) ) AND
             ( (Length(pattern)=0) OR (Pos(pattern,dat_files[i].Name)>0) ) THEN BEGIN
          IF NoEmptyFiles THEN BEGIN
            IF (dat_files[i].FileType AND $02)=0 THEN BEGIN
              SetLength(Result,Length(Result)+1);
              Result[High(Result)]:=dat_files[i].FileName;
            END;
          END ELSE BEGIN
            SetLength(Result,Length(Result)+1);
            Result[High(Result)]:=dat_files[i].FileName;
          END;
        END;
      END;
    END ELSE BEGIN
      where:='';
      IF Length(ext)>0 THEN BEGIN
        IF Length(where)>0 THEN where:=where+' AND ';
        where:=where+'(extension="'+ext+'")';
      END;
      IF Length(pattern)>0 THEN BEGIN
        IF Length(where)>0 THEN where:=where+' AND ';
        where:=where+'(name LIKE "%'+pattern+'%")';
      END;
      IF NoEmptyFiles THEN BEGIN
        IF Length(where)>0 THEN where:=where+' AND ';
        where:=where+'((contenttype & 2)=0)';
      END;
      IF Length(where)>0 THEN where:=' WHERE '+where;
      Tbl:=DB.GetTable('SELECT id,name,extension FROM datfiles'+where+' ORDER BY id ASC;');
      IF Tbl.Count>0 THEN BEGIN
        SetLength(Result,Tbl.Count);
        i:=0;
        REPEAT
          Result[i]:=FormatNumber(Tbl.FieldAsInteger('id'),5,'0')+'-'+Tbl.FieldAsString('name')+'.'+Tbl.FieldAsString('extension');
          Inc(i);
          Tbl.Next;
        UNTIL Tbl.EOF;
      END;
    END;
  END;

FUNCTION GetExtensionsList:TStringList;
  VAR
    i:LongWord;
    Tbl:TSQLiteTable;
  BEGIN
    SetLength(Result,0);
    IF opened_state=opened_dat THEN BEGIN
      FOR i:=0 TO dat_header.Extensions-1 DO BEGIN
        SetLength(Result,Length(Result)+1);
        WITH dat_extensionsmap[i] DO BEGIN
          Result[High(Result)]:=Extension[3]+Extension[2]+Extension[1]+Extension[0]+' ('+IntToStr(ExtCount)+')';
        END;
      END;
    END ELSE BEGIN
      Tbl:=DB.GetTable('SELECT extension,count(extension) AS x FROM datfiles GROUP BY extension ORDER BY extension ASC;');
      IF Tbl.Count>0 THEN BEGIN
        SetLength(Result,Tbl.Count);
        i:=0;
        REPEAT
          Result[i]:=Tbl.FieldAsString('extension')+' ('+IntToStr(Tbl.FieldAsInteger('x'))+')';
          Inc(i);
          Tbl.Next;
        UNTIL Tbl.EOF;
      END;
    END;
  END;

FUNCTION LoadDatInfos(filename:String):Boolean;
  VAR i:LongWord;
    dat_file:TFileStream;
  BEGIN
    Result:=True;
    opened_state:=opened_dat;
    dat_filename:=filename;
    raw_filename:=MidStr(filename,1,Length(filename)-3)+'raw';
    dat_file:=TFileStream.Create(filename, fmOpenRead);
    dat_file.Read(dat_header,SizeOf(dat_header));
    FOR i:=0 TO High(dat_header.Ident) DO
      IF dat_header.Ident[i]<>header_ident1[i] THEN BEGIN
        Result:=False;
        Exit;
      END;
    SetLength(dat_filesmap,dat_header.Files);
    SetLength(dat_files,dat_header.Files);
    FOR i:=0 TO dat_header.Files-1 DO dat_file.Read(dat_filesmap[i],SizeOf(dat_filesmap[i]));
    FOR i:=0 TO dat_header.Files-1 DO BEGIN
      dat_files[i].Extension:=dat_filesmap[i].Extension;
      dat_files[i].Extension:=ReverseString(dat_files[i].Extension);
      dat_files[i].Size:=dat_filesmap[i].FileSize;
      dat_files[i].FileType:=dat_filesmap[i].FileType;
      dat_files[i].DatAddr:=dat_filesmap[i].DataAddr-8+dat_header.DataAddr;
      IF (dat_filesmap[i].FileType AND $01)=0 THEN BEGIN
        dat_file.Seek(dat_filesmap[i].NameAddr+dat_header.NamesAddr,soFromBeginning);
        SetLength(dat_files[i].Name,100);
        dat_file.Read(dat_files[i].Name[1],100);
        dat_files[i].Name:=MidStr(dat_files[i].Name,1+4,Pos(#0,dat_files[i].Name)-1-4);
      END ELSE BEGIN
        dat_files[i].Name:='';
      END;
      dat_files[i].FileName:=FormatNumber(i,5,'0')+'-'+dat_files[i].Name+'.'+dat_files[i].Extension;
    END;
    dat_file.Seek($40+dat_header.Files*$14,soFromBeginning);
    SetLength(dat_namedfilesmap,dat_header.NamedFiles);
    FOR i:=0 TO dat_header.NamedFiles-1 DO dat_file.Read(dat_namedfilesmap[i],SizeOf(dat_namedfilesmap[i]));

    dat_file.Seek($40+dat_header.Files*$14+dat_header.NamedFiles*$8,soFromBeginning);
    SetLength(dat_extensionsmap,dat_header.Extensions);
    FOR i:=0 TO dat_header.Extensions-1 DO dat_file.Read(dat_extensionsmap[i],SizeOf(dat_extensionsmap[i]));

    dat_file.Free;
  END;


FUNCTION LoadDatFile(fileid:LongWord):Tdata;
  VAR
    dat_file:TFileStream;
    Tbl:TSQLiteTable;
    mem:TMemoryStream;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      dat_file:=TFileStream.Create(dat_filename, fmOpenRead);
      dat_file.Seek(dat_files[fileid].DatAddr,soFromBeginning);
      SetLength(Result,dat_files[fileid].Size);
      dat_file.Read(Result[0],dat_files[fileid].Size);
      dat_file.Free;
    END ELSE BEGIN
      Tbl:=DB.GetTable('SELECT data FROM datfiles WHERE id='+IntToStr(fileid)+';');
      IF Tbl.Count>0 THEN BEGIN
        mem:=Tbl.FieldAsBlob('data');
        SetLength(Result,mem.Size);
        mem.Seek(0,soFromBeginning);
        mem.Read(Result[0],mem.Size);
        mem.Free;
      END;
    END;
  END;
PROCEDURE UpdateDatFile(fileid:LongWord; data:Tdata);
  VAR
    dat_file:TFileStream;
    Tbl:TSQLiteTable;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      dat_file:=TFileStream.Create(dat_filename, fmOpenReadWrite);
      dat_file.Seek(dat_files[fileid].DatAddr,soFromBeginning);
      dat_file.Write(data[0],Length(data));
      dat_file.Free;
    END ELSE BEGIN
    END;
  END;

FUNCTION LoadDatFilePart(fileid,offset,size:LongWord; target:Pointer):Boolean;
  VAR
    dat_file:TFileStream;
    Tbl:TSQLiteTable;
    mem:TMemoryStream;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      dat_file:=TFileStream.Create(dat_filename, fmOpenRead);
      Result:=True;
      dat_file.Seek(dat_files[fileid].DatAddr+offset,soFromBeginning);
      dat_file.Read(target^,size);
      dat_file.Free;
    END ELSE BEGIN
      Tbl:=DB.GetTable('SELECT data FROM datfiles WHERE id='+IntToStr(fileid)+';');
      IF Tbl.Count>0 THEN BEGIN
        Result:=True;
        mem:=Tbl.FieldAsBlob('data');
        mem.Seek(offset,soFromBeginning);
        mem.Read(target^,size);
        mem.Free;
      END;
    END;
  END;
FUNCTION UpdateDatFilePart(fileid,offset,size:LongWord; target:Pointer):Boolean;
  VAR
    dat_file:TFileStream;
    Tbl:TSQLiteTable;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      dat_file:=TFileStream.Create(dat_filename, fmOpenReadWrite);
      Result:=True;
      dat_file.Seek(dat_files[fileid].DatAddr+offset,soFromBeginning);
      dat_file.Write(target^,size);
      dat_file.Free;
    END ELSE BEGIN
    END;
  END;

FUNCTION LoadRawFile(fileid,datlinkoffset,size:LongWord; target:Pointer):Boolean;
  VAR
    filestream:TFileStream;
    raw_addr:LongWord;
    Tbl:TSQLiteTable;
    mem:TMemoryStream;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      Result:=True;
      LoadDatFilePart(fileid,datlinkoffset,4,@raw_addr);
      filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.raw'),fmOpenRead);
      filestream.Seek(raw_addr,soFromBeginning);
      filestream.Read(target^,size);
      filestream.Free;
    END ELSE BEGIN
      Tbl:=DB.GetTable('SELECT data FROM rawmap WHERE (src_id='+IntToStr(fileid)+') AND (src_link_offset='+IntToStr(datlinkoffset)+');');
      IF Tbl.Count>0 THEN BEGIN
        Result:=True;
        mem:=Tbl.FieldAsBlob('data');
        mem.Seek(0,soFromBeginning);
        mem.Read(target^,size);
        mem.Free;
      END;
    END;
  END;
FUNCTION UpdateRawFile(fileid,datlinkoffset,size:LongWord; target:Pointer):Boolean;
  VAR
    filestream:TFileStream;
    raw_addr:LongWord;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      Result:=True;
      LoadDatFilePart(fileid,datlinkoffset,4,@raw_addr);
      filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.raw'),fmOpenReadWrite);
      filestream.Seek(raw_addr,soFromBeginning);
      filestream.Write(target^,size);
      filestream.Free;
    END ELSE BEGIN
    END;
  END;


FUNCTION FormatNumber(value:LongWord; width:Byte; leadingzeros:Char):String;
  BEGIN
    Result:=AnsiReplaceStr(Format('%'+IntToStr(width)+'u',[value]),' ',leadingzeros);
  END;

FUNCTION FormatFileSize(size:LongWord):String;
  BEGIN
    IF size>=1000*1024*1024 THEN BEGIN
      Result:=FloatToStrF(size/1024/1024/1024,ffFixed,5,1)+' GB';
    END ELSE BEGIN
      IF size>=1000*1024 THEN BEGIN
        Result:=FloatToStrF(size/1024/1024,ffFixed,5,1)+' MB';
      END ELSE BEGIN
        IF size>=1000 THEN BEGIN
          Result:=FloatToStrF(size/1024,ffFixed,5,1)+' KB';
        END ELSE BEGIN
          Result:=IntToStr(size)+' B';
        END;
      END;
    END;
  END;

FUNCTION CreateHexString(data:Tdata; HexOnly:Boolean):String;
  VAR
    string_build,ascii_version:String;
    i:LongWord;
  BEGIN
    string_build:='';
    ascii_version:='';
    FOR i:=0 TO High(data) DO BEGIN
      IF NOT HexOnly THEN
        IF (i MOD 16)=0 THEN
          string_build:=string_build+'0x'+IntToHex(i,6)+'  ';
      string_build:=string_build+IntToHex(data[i],2);
      IF NOT HexOnly THEN BEGIN
        IF data[i]>=32 THEN ascii_version:=ascii_version+Chr(data[i])
        ELSE ascii_version:=ascii_version+'.';
        IF ((i+1) MOD 2)=0 THEN string_build:=string_build+#32;
        IF ((i+1) MOD 16)=0 THEN BEGIN
          string_build:=string_build+#32+ascii_version+CrLf;
          ascii_version:='';
        END;
      END;
    END;
    Result:=string_build;
  END;


FUNCTION ExportFile(fileid:LongWord; filename:String; settings:TExportSet; path:String):Integer;
  VAR
    i:Byte;
    extension:String;
  BEGIN
    Result:=export_noerror;
    extension:=RightStr(filename,4);
    IF DO_dat IN settings THEN ExportDatFile(fileid,path+'\'+GetWinFileName(filename));
    IF DO_raw IN settings THEN BEGIN
      FOR i:=1 TO Length(ExportHandlers)+1 DO BEGIN
        IF i<=Length(ExportHandlers) THEN BEGIN
          IF ExportHandlers[i].Ext=extension THEN BEGIN
            IF ExportHandlers[i].needed THEN BEGIN
              CASE ExportHandlers[i].Handler(fileid,path+'\'+GetWinFileName(filename),(DO_convert IN settings)) OF
                0: Result:=0;
              ELSE
                Result:=export_handlererror;
              END;
            END;
            Break;
          END;
        END ELSE BEGIN
          Result:=export_nohandler;
        END;
      END;
    END;
  END;


FUNCTION GetWinFileName(name:String):String;
  BEGIN
    Result:=name;
    Result:=AnsiReplaceStr(Result,'\','__');
    Result:=AnsiReplaceStr(Result,'/','__');
    Result:=AnsiReplaceStr(Result,'>','__');
    Result:=AnsiReplaceStr(Result,'<','__');
  END;

FUNCTION GetExtractPath:String;
  BEGIN
    Result:=ExtractFilePath(dat_filename)+'\extracted_'+ExtractFileName(dat_filename);
  END;


PROCEDURE OpenDatabase(FileName:String);
  VAR
    i:Byte;
    data:Tdata;
    temps:String;
    Tbl: TSQLiteTable;
  BEGIN
    IF NOT FileExists(FileName) THEN BEGIN
      ShowMessage('File doesn''t exist!!!');
      Exit;
    END;
    DB:=TSQLiteDatabase.Create(FileName);
    Tbl:=DB.GetTable('SELECT name,value FROM globals ORDER BY name ASC');
    REPEAT
      IF Tbl.FieldAsString('name')='dbversion' THEN BEGIN
        IF Tbl.FieldAsString('value')<>DBversion THEN BEGIN
          ShowMessage('Database-file '+CrLf+'"'+FileName+'"'+CrLf+'has wrong version. (Required: '+DBversion+'; found: '+Tbl.FieldAsString('value')+')');
          Exit;
        END;
      END;
      IF Tbl.FieldAsString('name')='lvl' THEN BEGIN
        database_level:=StrToInt(Tbl.FieldAsString('value'));
      END;
      IF Tbl.FieldAsString('name')='ident' THEN BEGIN
        temps:=Tbl.FieldAsString('value');
        FOR i:=0 TO High(database_ident) DO BEGIN
          CASE temps[(i*2)+1+0] OF
            '0'..'9': database_ident[i]:=Ord(temps[(i*2)+1+0])-48;
            'A'..'F': database_ident[i]:=Ord(temps[(i*2)+1+0])-55;
          END;
          database_ident[i]:=database_ident[i]*16;
          CASE temps[(i*2)+1+1] OF
            '0'..'9': database_ident[i]:=database_ident[i]+Ord(temps[(i*2)+1+0])-48;
            'A'..'F': database_ident[i]:=database_ident[i]+Ord(temps[(i*2)+1+0])-55;
          END;
        END;
      END;
      Tbl.Next;
    UNTIL Tbl.EOF;
    Tbl.Free;
    opened_state:=opened_db;
  END;




END.
