UNIT Unit2_functions;
INTERFACE
USES Classes, Dialogs, SysUtils, StrUtils, Math,
      Unit3_data, ABSDecUtil, ABSMain, DB;

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 DataToBin(data:Tdata):String;
FUNCTION BinToInt(bin:String):Byte;

FUNCTION GetFileInfo(fileid:LongWord):TFileInfo;
FUNCTION LoadDatInfos(filename:String):Boolean;
PROCEDURE OpenDatabase(FileName:String);
PROCEDURE CloseDatabase;

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,dat_offset,raw_addr,size:LongWord; loc_sep:Boolean; target:Pointer):Boolean;
FUNCTION LoadRawFileByIDOffset(fileid,dat_offset:LongWord; target:Pointer):Boolean;
FUNCTION UpdateRawFile(rawinfo:TRawInfo; 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
USES Unit4_Exporters, Unit9_data_structures;

VAR
  Database:TABSDatabase;
  Query:TABSQuery;

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
    SetLength(Result,4);
    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;
    IF IsNAN(Result) THEN Result:=0.0;
  END;
FUNCTION Encode_Float(input:Single):Tdata;
  VAR _valueswitcher:TValueSwitcher;
  BEGIN
    _valueswitcher.ValueFloat:=input;
    Result:=Encode_Int(_valueswitcher.ValueInt);
  END;

FUNCTION DataToBin(data:Tdata):String;
  VAR
    i,j:Byte;
    singlebyte:Byte;
    bytepart:String;
  BEGIN
    SetLength(bytepart,8);
    Result:='';
    FOR i:=0 TO High(data) DO BEGIN
      singlebyte:=data[i];
      FOR j:=7 DOWNTO 0 DO BEGIN
        bytepart[j+1]:=Char((singlebyte AND $01)+48);
        singlebyte:=singlebyte SHR 1;
      END;
      Result:=Result+bytepart+' ';
    END;
  END;
FUNCTION BinToInt(bin:String):Byte;
  VAR
    Add: Integer;
    i: Byte;
  BEGIN
    Result:=0;
    IF Length(bin)<>8 THEN Exit;
    Add:=1;
    FOR i:=8 DOWNTO 1 DO BEGIN
      IF NOT (bin[i] IN ['0','1']) THEN Exit;
      IF bin[i] = '1' THEN Inc(Result,Add);
      Add:=Add SHL 1;
    END;
  END;

FUNCTION GetFileInfo(fileid:LongWord):TFileInfo;
  BEGIN
    IF opened_state=opened_dat THEN BEGIN
      Result:=dat_files[fileid];
    END ELSE BEGIN
      Query.SQL.Text:='SELECT * FROM datfiles WHERE id='+IntToStr(fileid)+' ORDER BY id ASC;';
      Query.Open;
      IF Query.RecordCount=1 THEN BEGIN
        Query.First;
        Result.ID:=Query.FieldByName('id').AsInteger;
        Result.Name:=Query.FieldByName('name').AsString;
        Result.Extension:=Query.FieldByName('extension').AsString;
        Result.FileName:=FormatNumber(Result.ID,5,'0')+'-'+Result.Name+'.'+Result.Extension;
        Result.Size:=Query.FieldByName('size').AsInteger;
        Result.FileType:=Query.FieldByName('contenttype').AsInteger;
        Result.DatAddr:=0;
        Result.opened:=False;
      END;
      Query.Close;
    END;
  END;


FUNCTION GetFilesList(ext:String; pattern:String; NoEmptyFiles:Boolean):TStringList;
  VAR
    i:LongWord;
    where:String;
    where_ext:String;
  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 (Pos(dat_files[i].Extension,ext)>0) ) AND
             ( (Length(pattern)=0) OR (Pos(UpperCase(pattern),UpperCase(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 ';
        IF Pos(',',ext)>0 THEN BEGIN
          i:=1;
          where_ext:='';
          WHILE i<Length(ext) DO BEGIN
            IF Length(where_ext)>0 THEN where_ext:=where_ext+' OR ';
            where_ext:=where_ext+'(extension="'+MidStr(ext,i,4)+'")';
            i:=i+5;
          END;
          where:=where+'('+where_ext+')';
        END ELSE BEGIN
          where:=where+'(extension="'+ext+'")';
        END;
      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)';
      END;
      IF Length(where)>0 THEN where:=' WHERE '+where;
      Query.SQL.Text:='SELECT id,name,extension FROM datfiles'+where+' ORDER BY id ASC;';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        Query.First;
        SetLength(Result,Query.RecordCount);
        i:=0;
        REPEAT
          Result[i]:=FormatNumber(Query.FieldByName('id').AsInteger,5,'0')+'-'+Query.FieldByName('name').AsString+'.'+Query.FieldByName('extension').AsString;
          Inc(i);
          Query.Next;
        UNTIL Query.EOF;
      END;
      Query.Close;
    END;
  END;

FUNCTION GetExtensionsList:TStringList;
  VAR
    i:LongWord;
  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
      Query.SQL.Text:='SELECT extension,count(extension) AS x FROM datfiles GROUP BY extension ORDER BY extension ASC;';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        SetLength(Result,Query.RecordCount);
        i:=0;
        REPEAT
          Result[i]:=Query.FieldByName('extension').AsString+' ('+IntToStr(Query.FieldByName('x').AsInteger)+')';
          Inc(i);
          Query.Next;
        UNTIL Query.EOF;
      END;
      Query.Close;
    END;
  END;

FUNCTION LoadDatInfos(filename:String):Boolean;
  VAR i:LongWord;
    dat_file:TFileStream;
    header_pc,header_mac:Boolean;
  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));
    header_pc:=True;
    header_mac:=True;
    FOR i:=0 TO High(dat_header.Ident) DO BEGIN
      IF dat_header.Ident[i]<>header_ident1_pc[i] THEN BEGIN
        header_pc:=False;
      END;
      IF dat_header.Ident[i]<>header_ident1_mac[i] THEN BEGIN
        header_mac:=False;
      END;
    END;
    IF NOT (header_pc OR header_mac) THEN BEGIN
      Result:=False;
      Exit;
    END ELSE BEGIN
      IF (header_pc AND NOT header_mac) THEN
        dat_os_mac:=False
      ELSE
        IF (NOT header_pc AND header_mac) THEN
          dat_os_mac:=True
        ELSE BEGIN
          Result:=False;
          Exit;
        END;
    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;
    mem:TStream;
  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
      Query.SQL.Text:='SELECT data FROM datfiles WHERE id='+IntToStr(fileid)+';';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        mem:=Query.CreateBlobStream(Query.FieldByName('data'),bmRead);
        SetLength(Result,mem.Size);
        mem.Seek(0,soFromBeginning);
        mem.Read(Result[0],mem.Size);
        mem.Free;
      END;
      Query.Close;
    END;
  END;
PROCEDURE UpdateDatFile(fileid:LongWord; data:Tdata);
  VAR
    dat_file:TFileStream;
  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;
    mem:TStream;
  BEGIN
    Result:=False;
    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
      Query.SQL.Text:='SELECT data FROM datfiles WHERE id='+IntToStr(fileid)+';';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        mem:=Query.CreateBlobStream(Query.FieldByName('data'),bmRead);
        mem.Seek(offset,soFromBeginning);
        mem.Read(target^,size);
        mem.Free;
      END;
      Query.Close;
    END;
  END;
FUNCTION UpdateDatFilePart(fileid,offset,size:LongWord; target:Pointer):Boolean;
  VAR
    dat_file:TFileStream;
  BEGIN
    Result:=False;
    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,dat_offset,raw_addr,size:LongWord; loc_sep:Boolean; target:Pointer):Boolean;
  VAR
    filestream:TFileStream;
    mem:TStream;
  BEGIN
    Result:=False;
    IF opened_state=opened_dat THEN BEGIN
      Result:=True;
      IF NOT loc_sep THEN
        filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.raw'),fmOpenRead)
      ELSE
        filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.sep'),fmOpenRead);
      filestream.Seek(raw_addr,soFromBeginning);
      filestream.Read(target^,size);
      filestream.Free;
    END ELSE BEGIN
      Query.SQL.Text:='SELECT data FROM rawmap WHERE (src_id='+IntToStr(fileid)+') AND (src_link_offset='+IntToStr(dat_offset)+');';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        Result:=True;
        mem:=Query.CreateBlobStream(Query.FieldByName('data'),bmRead);
        mem.Seek(0,soFromBeginning);
        mem.Read(target^,size);
        mem.Free;
      END;
      Query.Close;
    END;
  END;
FUNCTION LoadRawFileByIDOffset(fileid,dat_offset:LongWord; target:Pointer):Boolean;
  VAR
    i:Byte;
    raw_info:TRawInfo;
    mem:TStream;
  BEGIN
    Result:=False;
    IF opened_state=opened_dat THEN BEGIN
      Result:=True;
      raw_info:=GetRawInfo(fileid,dat_offset);
      LoadRawFile(fileid,dat_offset,raw_info.raw_addr,raw_info.raw_size,raw_info.loc_sep,target);
    END ELSE BEGIN
      Query.SQL.Text:='SELECT data FROM rawmap WHERE (src_id='+IntToStr(fileid)+') AND (src_link_offset='+IntToStr(dat_offset)+');';
      Query.Open;
      IF Query.RecordCount>0 THEN BEGIN
        Result:=True;
        mem:=Query.CreateBlobStream(Query.FieldByName('data'),bmRead);
        mem.Seek(0,soFromBeginning);
        mem.Read(target^,mem.size);
        mem.Free;
      END;
      Query.Close;
    END;
  END;
FUNCTION UpdateRawFile(rawinfo:TRawInfo; target:Pointer):Boolean;
  VAR
    filestream:TFileStream;
  BEGIN
    Result:=False;
    IF opened_state=opened_dat THEN BEGIN
      Result:=True;
      IF NOT rawinfo.loc_sep THEN
        filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.raw'),fmOpenReadWrite)
      ELSE
        filestream:=TFileStream.Create(AnsiReplaceStr(dat_filename,'.dat','.sep'),fmOpenReadWrite);
      filestream.Seek(rawinfo.raw_addr,soFromBeginning);
      filestream.Write(target^,rawinfo.raw_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_toone IN settings THEN BEGIN
      ExportDatFile(fileid,path+'\'+GetWinFileName(filename));
    END ELSE BEGIN
      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;
  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;
    temps:String;
  BEGIN
    IF NOT FileExists(FileName) THEN BEGIN
      ShowMessage('File doesn''t exist!!!');
      Exit;
    END;
    Database:=TABSDatabase.Create(NIL);
    Database.DatabaseName:='OLDBcon';
    Database.DatabaseFileName:=FileName;
    Database.Open;
    Query:=TABSQuery.Create(Database);
    Query.DatabaseName:='OLDBcon';
    Query.SQL.Text:='SELECT [name],[value] FROM globals ORDER BY [name] ASC';
    Query.Open;
    Query.First;
    REPEAT
      IF Query.FieldByName('name').AsString='dbversion' THEN BEGIN
        IF Query.FieldByName('value').AsString<>DBversion THEN BEGIN
          ShowMessage('Database-file '+CrLf+'"'+FileName+'"'+CrLf+'has wrong version. (Required: '+DBversion+'; found: '+Query.FieldByName('value').AsString+')');
          Query.Close;
          CloseDatabase;
          Exit;
        END;
      END;
      IF Query.FieldByName('name').AsString='lvl' THEN BEGIN
        database_level:=StrToInt(Query.FieldByName('value').AsString);
      END;
      IF Query.FieldByName('name').AsString='ident' THEN BEGIN
        temps:=Query.FieldByName('value').AsString;
        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;
      Query.Next;
    UNTIL Query.Eof;
    Query.Close;
    opened_state:=opened_db;
  END;
PROCEDURE CloseDatabase;
  BEGIN
    Database.Close;
    Database.Free;
    opened_state:=opened_nothing;
  END;



END.
