Module BigFile

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Module BigFile

Beitrag von GPI »

Kennt sicher jeder, man programmiert ein Spiel oder Anwendungen und hat tausende Bilder, JPGs oder andere Dateien, die das Programm benötigt. Zum einem ist es unpraktisch, immer so viele Kleinstdateien mitzuliefen, zum anderen will man nicht, das andere diese Dateien modifizieren.
Man könnte jetzt das ganze in eine Zip oder 7z-Datei einpflegen und mit den entsprechenden Packbefehle das ganze regeln. Problem hier, man müsste immer die Archivdatei aktuell halten, wenn man eine Änderung in den Dateien vornimmt oder man neue hinzufügen will, zum anderen kann die natürlich auch wieder jeder öffnen.
Mein BigFile-Modul soll diese Lücke schließen. Der Code ist so gestaltet, dass wenn eine Datei nicht in Bigfile gefunden wird, dass dann auf das normale Dateisystem ausgewichen wird. Auch gibt es einen Unterschied, ob man mit Debug compiled (meist wohl über Compile&run). Wenn der Debugger läuft, wird davon ausgegangen, das die Dateien in der BigFile veraltet sind und die Dateien aus den Dateisystem werden bevorzugt werden. Ohne Debugger haben die BigFile-Dateien immer Vorrang vor lokalen.
Es ist auch möglich mehrere BigFiles einzulesen. Sollten sich Überschneidungen ergeben, werden immer die Dateien aus der zuletzt eingelesen Bigfile bevorzugt. So kann man sehr einfach Patchfiles kreieren, ohne das man die alte Bigfile komplett ersetzen zu müssen.
Die Größe des Moduls erklärt sich dadurch, das ich im Prinzip die Directory, ReadFile und Preference Befehle nachgebaut habe. So kann man wie gewohnt programmieren, ohne sich zu kümmern, wo die Datei jetzt liegen - schon in einer Bigfile oder in lokale Dateien - es ist egal.

Wie wird eine Bigfile erstellt
Die Erstellungsbefehle werden normalerweise aus"CompilerIf". Sinn ist, das man die Erstellung eigentlich nur braucht, wenn man die BigFiles erstellt, nicht aber beim eigentlichen Programm. Um das Modul "BigFileCreate" zu benutzen, muss man vor den Include-Befehl die Konstante #bigfile_create auf #True setzen.

Code: Alles auswählen

#bigfile_create=#True
XIncludeFile "BigFile.pbi"
Danach stehen folgende Proceduren zur Verfügung:
BigFileCreate::Create(file.s)
Erstellt und öffnet eine BigFile.

BigFileCreate::Close()
Schreibt die gepackte Indexinformation in die Datei und schließt sie. Damit ist die BigFile einsatzbereit.

BigFileCreate::AddDir(path.s,BigFileDir.s=".")
Fügt alle Dateien in angegeben Ordner und sämtliche Unterordner zur BigFile. Defaultmäßig werden sie in Hauptverzeichnis (".") hinzugefügt, kann man aber ändern.

BigFileCreate::AddFile(file.s,BigFileDir.s=".")
Fügt eine einzelne Datei dazu. Auch hier kann man den Unterordner in der BigFile angeben.

Wie greife ich auf eine BigFile zu?
Ob die Konstante #bigfile_create #True oder #False ist oder gar definiert ist, hat keine Bedeutung. Einfach nur die "BigFile.pbi" "includen" und fertig. Zugriff erfolgt über folgende Befehle:

BigFile::Load(file.s)
Lädt die Index-Daten der BigFile in den Speicher. Anschließend sind alle Dateien in der BigFile bekannt

BigFile::Catch(*mem)
Die BigFile kann auch schon in Speicher befinden. WICHTIG: Der Speicherbereich darf nicht freigegeben werden!
Das ganze funktioniert natürlich auch, wenn der Speicher sich in der DataSection mittels IncludeBinary sich befindet.

BigFile::Clear()
Löscht sämtliche Indexdateien. Wenn man auf eine BigFile zugreifen will, muss man sie anschließend neu laden.

BigFile::DebugList()
Nur mit Debugger verfügbar. In Debug-Fenster werden sämtliche Dateien ausgegeben, die in der BigFile sind.

BigFile::SetPath(path.s)
Wenn die Dateien nicht in der BigFile sind, werden sie CurrentDirectory() gesucht. Hier kann man einen anderen Pfad angeben.

Directory-Befehle
Die folgenden Befehle ersetzen die aus PureBasic bekannten Directory-Befehle. Benutzung ist identisch dazu. FinishExamine ist zwingend notwendig, sonst gibts ein Speicherleck.
Es werden sowohl die Dateien in BigFile als auch die in gesetzten Ordner berücksichtigt!

BigFile::Examine(path.s)
Gibt ein *handle zurück oder #False bei einen Fehler

BigFile::EntryType(*handle)
Gibt je nach Typ #PB_DirectoryEntry_Directory oder #PB_DirectoryEntry_File zurück.

BigFile::NextEntry(*handle)
#True, wenn es einen weiteren Eintrag gab, ansonsten #false.

BigFile::EntryIsBig(*handle)
#True, wenn es sich um eine Datei in der BigFile handelt oder #False, wenn es eine reale Datei ist.

BigFile::EntrySize(*handle)
Die Größe des Eintrags in Bytes.

BigFile::EntryName(*handle)
Gibt einen String mit den Namen zurück.

BigFile::FinishExamine(*handle)
Gibt das *handle wieder frei.

Dateisystembefehle
BigFile::Size(file.s)
Entspricht FileSize() - gibt die Dateigröße zurück, -2 für Ordner, -1 für nicht vorhanden.

BigFile::SaveFile(File.s,OutFile.s)
Speichert die Datei aus der Bigfile. Praktisch, wenn die Datei auf der Festplatte sein müssen. Ich empfehle dann aber den Temp-Ordner zum speichern.

Dateibefehle
Logischerweise kann man nur lesen. Wichtig: Da die Dateien gepackt sind, befindet sich nach den öffnen die Datei *vollständig* in Speicher.

BigFile::Open(file.s)
Gibt ein *handle zurück oder #false wenn ein Fehler aufgetreten ist.

BigFile::Close(*handle)
Schließt die Datei.

BigFile::EOB(*handle)
Entspricht EndOfFile EOF - hier halt EndOfBig

BigFile::LOB(*handle)
LengthOfFile LOF - hier EndOfBig

BigFile::POS(*handle)
Location LOC - hier Position

BigFile::Seek(*handle,pos,mode=#PB_Absolute)
Leseposition verschieben

BigFile::.s ReadS(*handle,flag=0,length.u=-1)
Vollständiger Nachbau von ReadString. Es werden die Flags #PB_Ascii,#PB_UTF8,#PB_Unicode und #PB_File_IgnoreEOL unterstützt. Bei UTF8 wird auch beachtet, das ein Zeichen mehrere Bytes groß sein können.

BigFile::ReadA(*handle)
BigFile::ReadB(*handle)
BigFile::ReadC(*handle)
BigFile::ReadD(*handle)
BigFile::ReadF(*handle)
BigFile::ReadI(*handle)
BigFile::ReadL(*handle)
BigFile::ReadQ(*handle)
BigFile::ReadU(*handle)
BigFile::ReadW(*handle)

Es wird der entsprechende Typ gelesen.

BigFile::ReadMem(*handle,*mem,len)
Entspricht ReadData(), es wird die Anzahl der gelesen Bytes zurückgegeben.

Catch*-Befehle
Die Routinen laden die entsprechende Datei in den Speicher, rufen den Catch-Befehl auf und gibt anschließend den Speicher wieder frei. Es wird jeweils das erzeugte Handle zurückgegeben oder halt #false, wenn es ein Problem gab.

BigFile::Image(id,file.s)
BigFile::JSON(id,file.s,flag=0)
BigFile::Music(id,file.s)
BigFile::Sound(id,file.s,flag=0)
BigFile::Sprite(id,file.s,flag=0)


Preference-Befehle
Ersatz für die Preference-Befehle. Auch hier kann man nur lesen und auch hier befindet sich die Datei solange im Speicher, bis man die Datei wieder schließt.
Ein großer Unterschied ist, das meine Befehle mit Handle arbeiten. Ansonsten sollten sich die Befehle identisch zu den Original-befehlen verhalten.

BigFile::OpenPref(file.s)
BigFile::ClosePref(*handle)
BigFile::ExaminePrefGroups(*handle)
BigFile::ExaminePrefKeys(*handle)
BigFile::NextPrefGroup(*handle)
BigFile::NextPrefKey(*handle)
BigFile::PrefGroup(*handle,name.s)
BigFile::PrefGroupName(*handle)
BigFile::PrefKeyName(*handle)
BigFile::PrefKeyValue(*handle)
BigFile::ReadPrefD(*handle,key.s,DefaultValue.d)
BigFile::ReadPrefF(*handle,key.s,DefaultValue.f)
BigFile::ReadPrefI(*handle,key.s,DefaultValue.i)
BigFile::ReadPrefL(*handle,key.s,DefaultValue.l)
BigFile::ReadPrefQ(*handle,key.s,DefaultValue.q)
BigFile::ReadPrefS(*handle,key.s,DefaultValue.s)


Einschränkungen
Dateien mit einer größe von Null können nicht eingefügt werden. Ich benutze die Größenangabe von Null als Ordnereintrag.
Desweiteren kann eine BigFile nur 2GB groß werden, da ich mit Absicht nur Longs für die Größenangaben und Positionsangaben benutze.
Da die Dateien ja gepackt vorhanden sind, ist das ganze natürlich Speicherlastig. Es muss zumindest kurzfristig sowohl die gepackte als auch ungepackte Datei in den Speicher gehalten werden muss. Sollte man also extrem große Dateien haben, empfiehlt es sich, diese eher nicht in eine BigFile zu packen.

Nachwort
Wie immer würde ich mich über Feedback, bugs oder sonstiges freuen. Ok, über Bugs nicht ;)
Und nicht vergessen: Wenn der Debugger läuft, werden lokale Dateien bevorzugt.
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: Module BigFile

Beitrag von GPI »

bigfile.pbi - am Schluss ist noch ein Beispiel, das natürlich nur bei mir funktionell ist, weil es meine lokalen Dateien packt. Es soll nur die prinzipielle Benutzung zeigen.

Code: Alles auswählen

;*
;* Module BigFile
;*
;* Version: 1.1
;* Date: 2015-09-14
;*
;* Written by GPI
;*

UseLZMAPacker()

;#bigfile_create=#True

CompilerIf Not Defined(bigfile_create,#PB_Constant)
  #bigfile_create=#False
CompilerEndIf



CompilerIf #bigfile_create
  DeclareModule BigFileCreate
    EnableExplicit
    #Id=$46474942; $42494746 ;'BIGF'
    #Version=$30313030;'0100'
    
    Declare Create(file.s)
    Declare Close()
    Declare AddDir(path.s,BigFileDir.s=".")
    Declare AddFile(file.s,BigFileDir.s=".")
    
    
  EndDeclareModule
  
  Module BigFileCreate
    Macro RemoveBackslash(p):If Right(p,1)="\" : p=Left(p,Len(p)-1):EndIf:EndMacro
    Macro IfSet(a,b):a=b:If a:EndMacro: ;endindent ;endindent
    Macro IfNotSet(a,b):a=b:If a=#False:EndMacro: ;endindent ;endindent
    
    Procedure _LoadData(*mem,memsize,file.s)
      Define in,len
      IfSet(in,ReadFile(#PB_Any,file))
        Len=Lof(in)
        If len>memsize
          CloseFile(in)
          ProcedureReturn 0
        EndIf
        
        ReadData(in,*mem,Lof(in))
        CloseFile(in)
        ProcedureReturn len
      EndIf
      ProcedureReturn 0
    EndProcedure    
        
    Structure indexFile
      Size.l;0=directory
      PackSize.l
      Start.l      
    EndStructure
    Structure index
      Map file.indexfile()
    EndStructure    
        
    Global NewMap index.index()
    
    Global BufLen
    Global *Buf
    Global *OutBuf
    Global out
    
    
    Procedure _ResizeBuf(size)
      Protected *newbuf
      Protected *newoutbuf
      If BufLen<Size
        BufLen=Size+1024*1024
        IfNotSet (*newbuf,ReAllocateMemory(*buf,BufLen, #PB_Memory_NoClear))
          ProcedureReturn #False
        EndIf
        *buf=*newbuf
        IfNotSet (*newOutBuf,ReAllocateMemory(*OutBuf,BufLen, #PB_Memory_NoClear))
          ProcedureReturn #False
        EndIf
        *OutBuf=*newoutbuf
      EndIf
      ProcedureReturn #True
    EndProcedure
    
    Procedure _Add(file.s,len,BigFileDir.s=".")
      Protected plen
      Protected ok=#False
      Protected str.s

      
      If _ResizeBuf(len)
        If _LoadData(*buf,buflen,file)=len
          IfSet(plen,CompressMemory(*buf,len,*OutBuf,BufLen,#PB_PackerPlugin_Lzma))
            index(BigFileDir)\file(GetFilePart(file))
            index()\file()\Size=len
            index()\file()\Start=Loc(out)
            index()\file()\PackSize=plen
            WriteData(out,*OutBuf,plen)
            ok=#True
          EndIf
        EndIf        
      EndIf
      ProcedureReturn ok
    EndProcedure
        
    Procedure _scan(path.s,bigpath.s=".",add.s=".")
      Protected dir,name.s
      Protected ok=#True
      Protected str.s
      IfSet (dir,ExamineDirectory(#PB_Any,path+"\"+add,"*.*"))
        While NextDirectoryEntry(dir) And ok
          name=DirectoryEntryName(dir)
          If DirectoryEntryType(dir)=#PB_DirectoryEntry_Directory
            If name<>"." And name<>".."
              ;index(add)\file(Name)\Size=0
              If add<>"."
                ok=_scan(path,bigpath,add+"\"+name)
              Else
                ok=_scan(path,bigpath,name)
              EndIf              
            EndIf
          Else
            If add="." 
              str=bigpath
            ElseIf bigpath="."
              str=add
            Else
              str=bigpath+"\"+add
            EndIf                          
            
            ok=_add(path+"\"+add+"\"+name,DirectoryEntrySize(dir),str)
          EndIf
        Wend
        FinishDirectory(dir)
      EndIf
      ProcedureReturn ok
    EndProcedure
    
    Procedure Create(file.s)
      Protected ok=#False
      BufLen=1024*1024*10 ;10 MB
      IfSet(*Buf,AllocateMemory(BufLen))
        IfSet(*OutBuf,AllocateMemory(BufLen))
          IfSet(out,CreateFile(#PB_Any,file))
            WriteLong(out,#Id);0
            WriteLong(out,#Version);4
            WriteLong(out,0)       ;8 startofindex
            ok=#True
          EndIf
        EndIf
      EndIf
      
      If ok=#False
        Close()
      EndIf
      ProcedureReturn ok
    EndProcedure

    
    Procedure Close()
      Protected IndexPos
      Protected len,plen
      Protected *pos
      Protected ok=#False
      If out
        IndexPos=Loc(out)
        
        len=0
        ForEach index()
          If MapSize(index()\file())>0
            len+2+StringByteLength(MapKey(index()),#PB_Unicode)
            ForEach index()\file()
              If index()\file()\size<>0
                len+2+StringByteLength(MapKey(index()\file()),#PB_Unicode)
                len+4;size            
                len+4;packsize
                len+4;start
              EndIf
            Next
            len+2
          EndIf
        Next
        len+2
        
        If _ResizeBuf(len)
          *pos=*buf
          ForEach index()
            Debug "Subdir:"+MapKey(index())
            If MapSize(index()\file())>0              
              PokeW(*pos,Len(MapKey(index()))):*pos+2
              *pos+PokeS(*pos, MapKey(index()) ,-1,#PB_Unicode|#PB_String_NoZero)
              ForEach index()\file()
                Debug "  "+MapKey(index()\file())+" "+Str(index()\file()\Size)
                If index()\file()\size<>0
                  PokeW(*pos, Len( MapKey( index()\file() ) )):*pos+2
                  *pos+PokeS(*pos, MapKey( index()\file() ) ,-1,#PB_Unicode|#PB_String_NoZero)
                  PokeL(*pos, index()\file()\Size ):*pos+4              
                  PokeL(*pos, index()\file()\PackSize):*pos+4
                  PokeL(*pos, index()\file()\Start):*pos+4
                EndIf
              Next
              PokeW(*pos,0):*pos+2
            EndIf
          Next
          PokeW(*pos,0):*pos+2
          
          CompilerIf #PB_Compiler_Debugger
            If *pos<>*buf+len
              Debug "Error Index > "+Hex(*pos)+" "+Hex(*buf+len)+" "+Str(len)
              End
            EndIf
          CompilerEndIf
                    
          IfSet(plen,CompressMemory(*buf,len,*OutBuf,buflen,#PB_PackerPlugin_Lzma))
            WriteLong(out,len)
            WriteLong(out,plen)
            WriteData(out,*OutBuf,plen)
            
            FileSeek(out,8)
            WriteLong(out,IndexPos)
            ok=#True
          EndIf
        EndIf
        
        CloseFile(out)
        out=0
      EndIf      
      
      
      If *buf
        FreeMemory(*buf)
        *buf=0
      EndIf
      If *OutBuf
        FreeMemory(*OutBuf)
        *OutBuf=0
      EndIf      
      ClearMap(index())
      ProcedureReturn ok
    EndProcedure
      
    Procedure AddDir(path.s,BigFileDir.s=".")  
      RemoveBackslash(path)
      RemoveBackslash(BigFileDir)
      If Left(BigFileDir,1)="\":BigFileDir=Right(BigFileDir,Len(BigFileDir)-1):EndIf
      If BigFileDir="":BigFileDir=".":EndIf
      ProcedureReturn _scan(path,BigFileDir)
    EndProcedure
    
    Procedure AddFile(file.s,BigFileDir.s=".")
      Protected len
      RemoveBackslash(BigFileDir)
      If Left(BigFileDir,1)="\":BigFileDir=Right(BigFileDir,Len(BigFileDir)-1):EndIf
      If BigFileDir="":BigFileDir=".":EndIf
      len=FileSize(file)
      If len>0
        ProcedureReturn _add(file,len,BigFileDir)
      EndIf      
      ProcedureReturn #False
    EndProcedure
    
  EndModule
  
CompilerEndIf

DeclareModule BigFile
  EnableExplicit
  Declare Load(file.s)
  Declare Catch(*mem)
  Declare Clear()
  
  CompilerIf #PB_Compiler_Debugger
    Declare DebugList()
  CompilerElse
    Macro DebugList():EndMacro
  CompilerEndIf 
  
  Declare SetPath(path.s)
  
  Declare Examine(path.s)
  Declare EntryType(*handle)
  Declare FinishExamine(*handle)
  Declare NextEntry(*handle)
  Declare EntryIsBig(*handle)
  Declare EntrySize(*handle)
  Declare.s EntryName(*handle)
  
  Declare Size(file.s)
  Declare SaveFile(File.s,OutFile.s)
  
  Declare Open(file.s)
  Declare Close(*handle)
  Declare.s ReadS(*handle,flag=0,length.u=-1)
  Declare EOB(*handle)
  Declare LOB(*handle)
  Declare POS(*handle)
  Declare Seek(*handle,pos,mode=#PB_Absolute)
  Declare.a ReadA(*handle)
  Declare.b ReadB(*handle)
  Declare.c ReadC(*handle)
  Declare.d ReadD(*handle)
  Declare.f ReadF(*handle)
  Declare.i ReadI(*handle)
  Declare.l ReadL(*handle)
  Declare.q ReadQ(*handle)
  Declare.u ReadU(*handle)
  Declare.w ReadW(*handle)    
  Declare ReadMem(*handle,*mem,len)
  
  Declare Image(id,file.s)
  Declare JSON(id,file.s,flag=0)
  Declare Music(id,file.s)
  Declare Sound(id,file.s,flag=0)
  Declare Sprite(id,file.s,flag=0)
  
  Declare OpenPref(file.s)
  Declare ClosePref(*handle)
  Declare ExaminePrefGroups(*handle)
  Declare ExaminePrefKeys(*handle)
  Declare NextPrefGroup(*handle)
  Declare NextPrefKey(*handle)
  Declare PrefGroup(*handle,name.s)
  Declare.s PrefGroupName(*handle)
  Declare.s PrefKeyName(*handle)    
  Declare.s PrefKeyValue(*handle)
  Declare.d ReadPrefD(*handle,key.s,DefaultValue.d)
  Declare.f ReadPrefF(*handle,key.s,DefaultValue.f)
  Declare.i ReadPrefI(*handle,key.s,DefaultValue.i)
  Declare.l ReadPrefL(*handle,key.s,DefaultValue.l)
  Declare.q ReadPrefQ(*handle,key.s,DefaultValue.q)
  Declare.s ReadPrefS(*handle,key.s,DefaultValue.s)
  
  
EndDeclareModule

Module BigFile    
  Macro IfSet(a,b):a=b:If a:EndMacro: ;endindent ;endindent
  Macro IfNotSet(a,b):a=b:If a=#False:EndMacro: ;endindent ;endindent
  Macro RemoveBackslash(p):If Right(p,1)="\" : p=Left(p,Len(p)-1):EndIf:EndMacro
  Macro IndexFile:index()\file():EndMacro
  Macro SetIndex(a,b):index(a)\file(b):EndMacro
  Macro FindIndex(a,b): Bool(FindMapElement(index(),a) And FindMapElement(index()\file(),b)) : EndMacro
  #Id=$46474942; $42494746 ;'BIGF'
  #Version=$30313030;'0100'
  
  Global RealPath.s
  
  CompilerIf #PB_Compiler_Thread
    Global MutexBigId=CreateMutex()
    Macro LockBigId:LockMutex(MutexBigId):EndMacro
    Macro UnLockBigId:UnlockMutex(MutexBigId):EndMacro
    Global MutexIndex=CreateMutex()
    Macro LockIndex:LockMutex(MutexIndex):EndMacro
    Macro UnLockIndex:UnlockMutex(MutexIndex):EndMacro
  CompilerElse
    Macro LockBigId:EndMacro
    Macro UnLockBigId:EndMacro
    Macro LockIndex:EndMacro
    Macro UnLockIndex:EndMacro
  CompilerEndIf
  
  #BigId_Mem=-2
  #BigId_File=-1
  Global NewList BigId.s()
  Procedure AddBigId(file.s)
    Protected ret
    LockBigId
    LastElement(BigId())
    AddElement(BigId())
    BigId()=file
    ret=ListIndex(BigId())
    UnLockBigId
    ProcedureReturn ret
  EndProcedure
  Procedure.s GetBigId(id)
    Protected ret.s
    LockBigId
    SelectElement(BigId(),id)
    ret=BigId()
    UnLockBigId
    ProcedureReturn ret
  EndProcedure
  
  Structure iFile
    Size.l;0=file
    PackSize.l
    Start.i
    BigId.l
  EndStructure
  Structure Index
    Map file.iFile()
  EndStructure    
  Global NewMap index.index()
  
  Procedure _scanIndex(*outbuf,BigId,*start=0)
    Protected *pos
    Protected file.s
    Protected slen
    Protected len
    Protected dir.s
    Protected str.s,addx.s,i
    
    LockIndex
    *pos=*outbuf
    Repeat
      slen=PeekW(*pos):*pos+2
      If slen=0
        Break
      EndIf
      dir=PeekS(*pos,slen,#PB_Unicode):*pos+slen+slen
      
      addx=".":i=0
      Repeat
        i+1
        str=StringField(dir,i,"\")
        If str
          index(addx)\file(str)\Size=0
          If addx=".":addx=str:Else:addx+"\"+str:EndIf
        Else
          Break
        EndIf
      ForEver
      
      
      Repeat
        slen=PeekW(*pos):*pos+2
        If slen=0
          Break
        EndIf
        file=PeekS(*pos,slen,#PB_Unicode):*pos+slen+slen
        
        SetIndex(dir,file)
        IndexFile\BigId=BigId
        
        len=PeekL(*pos):*pos+4
        IndexFile\Size=len
        
        If len
          IndexFile\PackSize=PeekL(*pos):*pos+4
          IndexFile\Start=PeekL(*pos)+*start:*pos+4
        EndIf
      ForEver
    ForEver
    UnLockIndex
  EndProcedure
  Procedure Catch(*mem)
    Protected StartIndex
    Protected len,plen
    Protected *outbuf
    Protected ok=#False
    
    
    If PeekL(*mem)<>#id Or PeekL(*mem+4)<>#version
      ProcedureReturn #False
    EndIf
    StartIndex=PeekL(*mem+8)
    len=PeekL(*mem+StartIndex)
    plen=PeekL(*mem+StartIndex+4)
    
    IfSet(*outbuf,AllocateMemory(len))
      If UncompressMemory(*mem+StartIndex+8,plen,*outbuf,len,#PB_PackerPlugin_Lzma)=len
        _scanIndex(*outbuf,#BigId_Mem,*mem)
        ok=#True
      EndIf        
      FreeMemory(*outbuf)
    EndIf
    ProcedureReturn ok
  EndProcedure     
  Procedure Load(file.s)
    Protected in
    Protected StartIndex
    Protected len,plen
    Protected *buf,*outbuf
    Protected ok=#False
    Protected BigId
    
    
    BigId=AddBigId(file)
    
    IfSet(in,ReadFile(#PB_Any,file,#PB_File_SharedRead))
      If ReadLong(in)<>#id
        CloseFile(in)
        ProcedureReturn #False
      EndIf
      If ReadLong(in)<>#Version
        CloseFile(in)
        ProcedureReturn #False
      EndIf
      
      StartIndex=ReadLong(in)
      FileSeek(in,StartIndex)
      
      len=ReadLong(in)
      plen=ReadLong(in)
      
      IfSet(*buf,AllocateMemory(len+plen))        
        *outbuf=*buf+plen
        ReadData(in,*buf,plen)
        
        If UncompressMemory(*buf,plen,*outbuf,len,#PB_PackerPlugin_Lzma)=len
          _scanIndex(*outbuf,BigId)
          ok=#True
        EndIf
        
        FreeMemory(*buf)
      EndIf
      CloseFile(in)
    EndIf
    
    ProcedureReturn ok
  EndProcedure
  
  Procedure Clear()
    LockIndex
    LockBigId
    ClearMap(index())
    ClearList(BigId())
    UnLockIndex
    UnLockBigId
  EndProcedure
  
  
  CompilerIf #PB_Compiler_Debugger
    Procedure DebugList()
      LockIndex
      ForEach index()
        Debug "Dir:"+MapKey(index())
        ForEach index()\file()
          If IndexFile\Size=0
            Debug "["+MapKey(IndexFile)+"]"
          Else
            Debug MapKey(IndexFile)+"  "+IndexFile\size+"  "+IndexFile\packsize+"  s:"+IndexFile\start+"  id:"+IndexFile\BigId
          EndIf
          
        Next
        Debug ""
      Next
      UnLockIndex
    EndProcedure
  CompilerEndIf
  
  Procedure SetPath(path.s)
    RemoveBackslash(path)
    If path="":path="." : EndIf
    RealPath=path
  EndProcedure
  
  
  Procedure _ScanDir(path.s,*handle.index)
    Protected dir
    Protected name.s
    Protected size
    IfSet (dir,ExamineDirectory(#PB_Any,RealPath+"\"+path,"*.*"))
      While NextDirectoryEntry(dir)
        name=DirectoryEntryName(dir)
        If DirectoryEntryType(dir)=#PB_DirectoryEntry_Directory
          If name<>"." And name<>".."
            *handle\file(name)\Size=0
          EndIf            
        Else
          size=DirectoryEntrySize(dir)
          If size>0
            *handle\file(name)\Size=size
            *handle\file()\BigId=#BigId_File             
          EndIf            
        EndIf          
      Wend
      FinishDirectory(dir)
    EndIf
  EndProcedure
  Procedure _ScanBig(path.s,*handle.index)
    LockIndex
    If FindMapElement(index(),path)
      ForEach index()\file()
        *handle\file(MapKey(IndexFile))
        *handle\file()\BigId=IndexFile\BigId
        *handle\file()\PackSize=IndexFile\PackSize
        *handle\file()\Size=IndexFile\Size
        *handle\file()\Start=IndexFile\Start
      Next
    EndIf
    UnLockIndex
  EndProcedure
  
  Procedure Examine(path.s)
    Protected *handle.index
    RemoveBackslash(path)
    If path="":path="." : EndIf
    
    IfSet(*handle,AllocateStructure(Index))
      CompilerIf #PB_Compiler_Debugger
        _ScanBig(path,*handle) ;debugger = dir > big
        _ScanDir(path,*handle)
      CompilerElse
        _ScanDir(path,*handle) ;execute = big < dir
        _ScanBig(path,*handle)
      CompilerEndIf        
      ResetMap(*handle\file())
    EndIf
    ProcedureReturn *handle
  EndProcedure
  Procedure EntryType(*handle.index)
    If *handle\file()\Size=0
      ProcedureReturn #PB_DirectoryEntry_Directory
    Else
      ProcedureReturn #PB_DirectoryEntry_File
    EndIf
  EndProcedure
  Procedure FinishExamine(*handle)
    FreeStructure(*handle)
  EndProcedure
  Procedure NextEntry(*handle.index)
    ProcedureReturn NextMapElement(*handle\file())
  EndProcedure
  Procedure EntryIsBig(*handle.index)
    ProcedureReturn Bool(*handle\file()\BigId<>#BigId_File)
  EndProcedure
  Procedure EntrySize(*handle.index)
    ProcedureReturn *handle\file()\Size
  EndProcedure
  Procedure.s EntryName(*handle.index)
    ProcedureReturn MapKey(*handle\file())
  EndProcedure
  
  Procedure Size(file.s)
    Protected path.s
    Protected ret
    Protected realfile.s
    path=GetPathPart(file)
    RemoveBackslash(path)
    If Path="" : path="." : EndIf
    
    If Left(file,1)="\"
      realfile=RealPath+file
    Else
      realfile=RealPath+"\"+file
    EndIf
    
    LockIndex
    CompilerIf #PB_Compiler_Debugger
      ret=FileSize(realfile);  dir > big
      If ret=-1 Or ret=0
        If FindIndex(path,file)
          ret=IndexFile\Size
          If ret=0
            ret=-2
          EndIf
        Else
          ret=-1
        EndIf
      EndIf
    CompilerElse
      If FindIndex(path,file)
        ret=IndexFile\Size
        If ret=0
          ret=-2
        EndIf
      Else
        ret=FileSize(realfile)
      EndIf
    CompilerEndIf
    UnLockIndex
    
    ProcedureReturn ret
  EndProcedure
  
  Procedure _LoadBig(file.s,offset=0,addlen=0)
    Protected path.s
    Protected *mem,*mempack
    Protected big.s
    Protected ok=#False
    Protected in
    
    path=GetPathPart(file)
    RemoveBackslash(path)
    If Path="" : path="." : EndIf
    
    LockIndex
    If FindIndex(path,GetFilePart(file))
      IfSet(*mem,AllocateMemory(IndexFile\Size+offset+addlen))
        
        If IndexFile\BigId=#BigId_Mem
          *mempack=IndexFile\Start
          ok=#True
        Else
          IfSet(*mempack,AllocateMemory(IndexFile\PackSize))
            big=GetBigId(IndexFile\BigId)
            IfSet(in,ReadFile(#PB_Any,big,#PB_File_SharedRead|#PB_File_NoBuffering))
              FileSeek(in,IndexFile\Start)
              If ReadData(in,*mempack,IndexFile\PackSize)=IndexFile\PackSize
                ok=#True
              EndIf
              CloseFile(in)
            EndIf
          EndIf
        EndIf
        
        If ok
          If UncompressMemory(*mempack,IndexFile\PackSize,*mem+offset,IndexFile\Size,#PB_PackerPlugin_Lzma)<>IndexFile\Size
            ok=#False
          EndIf
        EndIf              
        
        If IndexFile\BigId<>#BigId_Mem And *mempack
          FreeMemory(*mempack)
        EndIf
        If ok=#False
          FreeMemory(*mem)
          *mem=0
        EndIf
        
      EndIf
    EndIf
    UnLockIndex
    
    ProcedureReturn *mem
  EndProcedure
  Procedure _LoadFile(file.s,offset=0,addlen=0)
    Protected *mem
    Protected ok=#False
    Protected in
    
    If Left(file,1)="\"
      file=RealPath+file
    Else
      file=RealPath+"\"+file
    EndIf
    
    IfSet (in, ReadFile(#PB_Any,file,#PB_File_SharedRead|#PB_File_NoBuffering))
      IfSet(*mem,AllocateMemory(Lof(in)+offset+addlen))
        If ReadData(in,*mem+offset,Lof(in))=Lof(in)
          ok=#True
        EndIf
      EndIf
      CloseFile(in)
    EndIf
    
    If ok
      ProcedureReturn *mem
    ElseIf *mem
      FreeMemory(*mem)
    EndIf
    ProcedureReturn 0
  EndProcedure
  Procedure _LoadMem(file.s,offset=0,addlen=0)
    Protected *mem
    CompilerIf #PB_Compiler_Debugger
      IfNotSet(*mem,_LoadFile(file,offset,addlen))
        *mem=_LoadBig(file,offset,addlen)
      EndIf
    CompilerElse
      IfNotSet(*mem,_LoadBig(file,offset,addlen))
        *mem=_LoadFile(File,offset,addlen)
      EndIf        
    CompilerEndIf
    ProcedureReturn *mem
  EndProcedure
  
  Procedure SaveFile(File.s,OutFile.s)
    Protected *mem
    Protected out
    Protected size
    Protected ok=#False
    
    IfSet(*mem,_LoadMem(file))
      size=size(file)
      IfSet(out,CreateFile(#PB_Any,OutFile))
        If WriteData(out,*mem,size)=size
          ok=#True
        EndIf                
        CloseFile(out)
      EndIf
      FreeMemory(*mem)
    EndIf
    
    ProcedureReturn OK
  EndProcedure    
  
  Structure filehandle
    pos.l
    size.l
    forceflag.l
    Dat.b
  EndStructure    
  
  Procedure Open(file.s)
    Protected *mem.filehandle
    
    IfSet(*mem,_LoadMem(file,OffsetOf(filehandle\Dat),10))
      *mem\size=Size(file)
      
      CompilerIf #PB_Compiler_Unicode
        *mem\forceflag=#PB_UTF8
      CompilerElse
        *mem\forceflag=#PB_Ascii  
      CompilerEndIf
      
    EndIf
    ProcedureReturn *mem      
  EndProcedure
  Procedure Close(*handle)
    FreeMemory(*handle)
  EndProcedure
  Procedure.s ReadS(*handle.filehandle,allflag=0,length.u=-1)
    Protected *posu.unicode
    Protected *posa.ascii
    Protected *start
    Protected ret.s="Error"
    Protected savea.a,saveu.u
    Protected readed.u
    Protected flag=allflag & (#PB_Unicode|#PB_UTF8|#PB_Ascii)
    Protected IgnoreFlag=allflag & #PB_File_IgnoreEOL
    
    If *handle\pos>=*handle\size
      ProcedureReturn ""
    EndIf
    
    ;bom check
    *posa=*handle+OffsetOf(filehandle\Dat)+*handle\pos
    If *posa\a=$ef
      *posa+1
      If *posa\a=$bb
        *posa+1
        If *posa\a=$bf
          flag=#PB_UTF8
          *handle\forceflag=flag
          *handle\pos+3
        EndIf
      EndIf
    ElseIf *posa\a=$FF
      *posa+1
      If *posa\a=$FE
        flag=#PB_Unicode
        *handle\forceflag=flag
        *handle\pos+2
      EndIf
    EndIf
    If flag=0
      flag=*handle\forceflag
    EndIf
    
    Select flag
      Case #PB_Ascii,#PB_UTF8
        *posa=*handle+OffsetOf(filehandle\Dat)+*handle\pos
        *start=*posa
        Repeat
          If (Ignoreflag=0 And (*posa\a=13 Or *posa\a=10)) Or *posa\a=0 Or *handle\pos>=*handle\size Or readed>=length
            savea=*posa\a
            *posa\a=0
            ret=PeekS(*start,-1,flag)
            *posa\a=savea
            If Ignoreflag=0
              If *posa\a=0
                *posa+1
                *handle\pos+1
              Else
                If *posa\a=13
                  *posa+1
                  *handle\pos+1
                EndIf
                If *posa\a=10
                  *posa+1
                  *handle\pos+1
                EndIf
              EndIf
            EndIf
            Break
            
          EndIf         
          If flag=#PB_UTF8
            ;start of a multi-byte?
            ;              87654321   87654321
            If (*posa\a & %11000000)=%11000000
              Repeat
                *posa+1
                *handle\pos+1
                ;                87654321    87654321
              Until (*posa\a & %11000000)<>%10000000
              readed+1
            Else
              *posa+1
              *handle\pos+1
              readed+1
            EndIf              
          Else            
            *posa+1
            *handle\pos+1
            readed+1
          EndIf            
        ForEver
      Case #PB_Unicode
        *posu=*handle+OffsetOf(filehandle\Dat)+*handle\pos
        *start=*posu
        Repeat
          If (ignoreFlag=0 And (*posu\u=13 Or *posu\u=10)) Or *posu\u=0 Or *handle\pos>=*handle\size Or readed>=length
            saveu=*posu\u
            *posu\u=0
            ret=PeekS(*start,-1,flag)
            *posu\u=saveu
            If Ignoreflag=0
              If *posu\u=0
                *posu+2
                *handle\pos+2
              Else
                If *posu\u=13
                  *posu+2
                  *handle\pos+2
                EndIf
                If *posu\u=10
                  *posu+2
                  *handle\pos+2
                EndIf
              EndIf
            EndIf
            Break
            
          EndIf
          *posu+2
          *handle\pos+2
          readed+1
        ForEver
    EndSelect
    
    ProcedureReturn ret
  EndProcedure
  Procedure EOB(*handle.filehandle)
    ProcedureReturn Bool(*handle\pos>=*handle\size)
  EndProcedure
  Procedure LOB(*handle.filehandle)
    ProcedureReturn *handle\size
  EndProcedure
  Procedure POS(*handle.filehandle)
    ProcedureReturn *handle\pos
  EndProcedure
  
  Procedure Seek(*handle.filehandle,pos,mode=#PB_Absolute)
    If mode=#PB_Absolute
      *handle\pos=pos
    Else
      *handle\pos+pos
    EndIf
    If *handle\pos<0
      *handle\pos=0
    ElseIf *handle\pos>*handle\size
      *handle\pos=*handle\size
    EndIf
  EndProcedure
  
  Macro Readx(i,in)
    Procedure.i Read#i(*handle.filehandle)
      Protected *var.in=*handle+OffsetOf(filehandle\dat)+*handle\pos
      *handle\pos+SizeOf(in)
      ProcedureReturn *var\i
    EndProcedure
  EndMacro
  
  readx(a,ascii)
  readx(b,byte)
  readx(c,character)
  readx(d,double)
  readx(f,float)
  readx(i,integer)
  readx(l,long)
  readx(q,quad)
  readx(u,unicode)
  readx(w,word)
  
  Procedure ReadMem(*handle.filehandle,*mem,len)
    Protected max
    max=*handle\size-*handle\pos
    If len>=max : len=max : EndIf
    CopyMemory(*handle+OffsetOf(filehandle\Dat)+*handle\pos,*mem,len)
    *handle\pos+len
    ProcedureReturn len
  EndProcedure
  
  Procedure Image(id,file.s)
    Protected *mem
    Protected ret
    IfSet (*mem,_LoadMem(file))
      ret=CatchImage(id,*mem,size(file))
      FreeMemory(*mem)
    EndIf
    ProcedureReturn ret
  EndProcedure
  Procedure JSON(id,file.s,flag=0)
    Protected *mem
    Protected ret
    IfSet( *mem,_LoadMem(file))
      If flag=0 
        ret=CatchJSON(id,*mem,size(file))
      Else
        ret=CatchJSON(id,*mem,size(file),flag)
      EndIf        
      FreeMemory(*mem)
    EndIf
    ProcedureReturn ret
  EndProcedure
  Procedure Music(id,file.s)
    Protected *mem
    Protected ret
    IfSet(*mem,_LoadMem(file))
      ret=CatchMusic(id,*mem,size(file))
      FreeMemory(*mem)
    EndIf
    ProcedureReturn ret
  EndProcedure
  Procedure Sound(id,file.s,flag=0)
    Protected *mem
    Protected ret
    IfSet (*mem,_LoadMem(file))
      If flag=0 
        ret=CatchSound(id,*mem,size(file))
      Else
        ret=CatchSound(id,*mem,size(file),flag)
      EndIf        
      FreeMemory(*mem)
    EndIf
    ProcedureReturn ret
  EndProcedure
  Procedure Sprite(id,file.s,flag=0)
    Protected *mem
    Protected ret
    IfSet (*mem,_LoadMem(file))
      If flag=0 
        ret=CatchSprite(id,*mem)
      Else
        ret=CatchSprite(id,*mem,flag)
      EndIf        
      FreeMemory(*mem)
    EndIf
    ProcedureReturn ret
  EndProcedure 
  
  Structure pref_key
    Map key.string()
  EndStructure
  Structure pref
    valid.i
    Map group.pref_key()
  EndStructure
  
  Procedure OpenPref(file.s)
    Protected *handle.pref
    Protected in
    Protected ok=#False
    Protected str.s
    Protected group.s,key.s,value.s
    Protected a
    IfSet (*handle,AllocateStructure(pref))
      IfSet(in,Open(file))
        While Not eob(in)
          str=Trim(reads(in))
          Select Left(str,1)
            Case ";";nothing
            Case "[";group
              group=Mid(str,2,Len(str)-2)
            Default
              a=FindString(str,"=")
              If a
                key=Trim(Left(str,a-1))
                value=Trim(Right(str,Len(str)-a))
                *handle\group(group)\key(key)\s=value
              EndIf
          EndSelect            
        Wend
        ok=#True
        close(in)
      EndIf        
    EndIf      
    
    If ok=#False
      If *handle
        FreeStructure(*handle)
      EndIf
      *handle=0
    Else
      If FindMapElement(*handle\group(),"")
        ResetMap(*handle\group()\key())
        *handle\valid=#True
      Else
        *handle\valid=#False
      EndIf        
    EndIf
    ProcedureReturn *handle
  EndProcedure
  Procedure ClosePref(*handle.pref)
    FreeStructure(*handle)
  EndProcedure
  Procedure ExaminePrefGroups(*handle.pref)
    ResetMap(*handle\group())
    *handle\valid=#False
    ProcedureReturn #True
  EndProcedure
  Procedure ExaminePrefKeys(*handle.pref)
    If *handle\valid
      ResetMap(*handle\group()\key())
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf      
  EndProcedure
  Procedure NextPrefGroup(*handle.pref)
    If NextMapElement(*handle\group())
      *handle\valid=#True
    Else
      *handle\valid=#False
    EndIf
    ProcedureReturn *handle\valid
  EndProcedure
  Procedure NextPrefKey(*handle.pref)
    If *handle\valid
      ProcedureReturn NextMapElement(*handle\group()\key())
    Else
      ProcedureReturn #False
    EndIf      
  EndProcedure
  Procedure PrefGroup(*handle.pref,name.s)
    If FindMapElement(*handle\group(),name)
      ResetMap(*handle\group()\key())
      *handle\valid=#True        
    Else
      ResetMap(*handle\group())
      *handle\valid=#False
    EndIf      
    ProcedureReturn *handle\valid
  EndProcedure
  Procedure.s PrefGroupName(*handle.pref)
    If *handle\valid
      ProcedureReturn MapKey(*handle\group())
    EndIf
    ProcedureReturn ""
  EndProcedure
  Procedure.s PrefKeyName(*handle.pref)
    If *handle\valid
      ProcedureReturn MapKey(*handle\group()\key())
    EndIf
    ProcedureReturn ""
  EndProcedure
  Procedure.s PrefKeyValue(*handle.pref)
    If *handle\valid
      ProcedureReturn *handle\group()\key()\s
    EndIf
    ProcedureReturn""
  EndProcedure
  
  Macro ReadPrefX(d)
    Procedure.d ReadPref#d(*handle.pref,key.s,DefaultValue.d)
      If *handle\valid And FindMapElement(*handle\group()\key(),key)
        ProcedureReturn Val#d(*handle\group()\key()\s)
      EndIf
      ProcedureReturn DefaultValue
    EndProcedure
  EndMacro
  ReadPrefX(d)
  ReadPrefX(f)
  Macro ReadPrefY(q)
    Procedure.q ReadPref#q(*handle.pref,key.s,DefaultValue.q)
      If *handle\valid And FindMapElement(*handle\group()\key(),key)
        ProcedureReturn Val(*handle\group()\key()\s)
      EndIf
      ProcedureReturn DefaultValue
    EndProcedure
  EndMacro
  ReadPrefY(i)
  ReadPrefY(l)
  ReadPrefY(q)    
  
  Procedure.s ReadPrefS(*handle.pref,key.s,DefaultValue.s)
    If *handle\valid And FindMapElement(*handle\group()\key(),key)
      ProcedureReturn *handle\group()\key()\s
    EndIf
    ProcedureReturn DefaultValue
  EndProcedure
  
EndModule



CompilerIf #PB_Compiler_IsMainFile
  CompilerIf #bigfile_create
    If BigFileCreate::Create("E:\purebasic\Temp\Bigdata.dat")
      Debug BigFileCreate::AddDir("E:\purebasic\Temp\Bigdata")
      Debug BigFileCreate::Close()
    EndIf
    If BigFileCreate::Create("E:\purebasic\Temp\Bigdata2.dat")
      Debug BigFileCreate::AddDir("E:\purebasic\Temp\Bigdata2")
      Debug BigFileCreate::AddFile("E:\purebasic\Temp\Data\PureBasic.prefs","hidden\very\very\hidden")
      Debug BigFileCreate::close()
    EndIf    
  CompilerEndIf
  
  ;using
  OpenConsole()
  PrintN("Load bigdata:")
  PrintN(Str(BigFile::Load("E:\purebasic\Temp\Bigdata.dat")))
  ;PrintN(Str(BigFile::Load("E:\purebasic\Temp\Bigdata2.dat")))
  PrintN(Str(BigFile::Catch(?internbig)))
  
  bigfile::DebugList()
  
  
  
  BigFile::SetPath("E:\PureBasic\Temp\Data\")
  
  BigFile::DebugList()
  
  Debug BigFile::SaveFile("german.html","E:\purebasic\Temp\German.html")
  Debug BigFile::SaveFile("Data\1.wav", "e:\purebasic\temp\1.wav")
  
  
  
  PrintN("Examine \")
  Define handle
  handle=BigFile::Examine("\")
  If handle
    While BigFile::NextEntry(handle)
      If bigfile::EntryType(handle)=#PB_DirectoryEntry_Directory
        PrintN("["+BigFile::EntryName(handle)+"]")
      Else
        PrintN(BigFile::EntryName(handle)+" "+bigfile::EntrySize(handle)+" "+bigfile::EntryIsBig(handle))
      EndIf
    Wend
    bigfile::FinishExamine(handle)
  EndIf
  PrintN("-----")
  
  PrintN("Examine Img")
  handle=BigFile::Examine("Img")
  If handle
    While BigFile::NextEntry(handle)
      If bigfile::EntryType(handle)=#PB_DirectoryEntry_Directory
        PrintN("["+BigFile::EntryName(handle)+"]")
      Else
        PrintN(BigFile::EntryName(handle)+" "+bigfile::EntrySize(handle)+" "+bigfile::EntryIsBig(handle))
      EndIf
    Wend
    bigfile::FinishExamine(handle)
  EndIf
  
  PrintN("File: controlmap.txt "+BigFile::Size("controlmap.txt"))
  
  Define in
  Define str.s
  Define i=0,a
  in=BigFile::open("German.html")
  If in
    str=bigfile::reads(in,#PB_File_IgnoreEOL,10)
    PrintN( "  limit:"+str)
    While Not BigFile::eob(in)
      str=BigFile::ReadS(in)
      PrintN("  Line:"+str)
      Debug "  Line:"+str
      
      i+1:If i=5
        Break
      EndIf
      
    Wend      
    
    BigFile::close(in)
  EndIf
  
  Define in
  in=BigFile::OpenPref("pref.pref")
  Debug in
  If in
    Debug "PREF"
    bigFile::ExaminePrefGroups(in)
    While bigfile::NextPrefGroup(in)
      PrintN("["+ bigfile::PrefGroupName(in) +"]")
      Debug "["+ bigfile::PrefGroupName(in) +"]"
      BigFile::ExaminePrefKeys(in)
      While bigfile::NextPrefKey(in)
        PrintN("  "+BigFile::PrefKeyName(in))
        Debug"  "+BigFile::PrefKeyName(in)
      Wend
      
    Wend
    
    bigfile::PrefGroup(in,"common")
    PrintN(BigFile::ReadPrefS(in,"aber","defstr"))
    bigfile::PrefGroup(in,"")
    PrintN(StrD(BigFile::ReadPrefd(in,"double",123),2))
    PrintN(StrF(BigFile::ReadPreff(in,"float",123),2))
    
    BigFile::ClosePref(in)
  EndIf
  
   
  UsePNGImageDecoder()
  pic=BigFile::Image( #PB_Any, "data\freestyle.bmp")

  If OpenWindow(0,0,0,200,200,"test",#PB_Window_SystemMenu)
    ImageGadget(0,0,0,200,200,ImageID(pic))
    While WaitWindowEvent()<>#PB_Event_CloseWindow
    Wend
    CloseWindow(0)
  EndIf
  
  bigfile::clear()
  
  DataSection
    internbig:
    IncludeBinary "E:\purebasic\Temp\Bigdata2.dat"
  EndDataSection
  Input()
CompilerEndIf



Zuletzt geändert von GPI am 15.09.2015 13:04, insgesamt 1-mal geändert.
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Module BigFile

Beitrag von STARGÅTE »

Hallo GPI,

obwohl mich der Titel erst etwas anderes erwarten lies, so ist "Der Code ist so gestaltet, dass wenn eine Datei nicht in Bigfile gefunden wird, dass dann auf das normale Dateisystem ausgewichen wird." eine gute Idee!
Hier aus dem flexiblen Ordnersystem und einer gepackten Datei die guten Sachen zu ziehen, ist sicher eine Erleichterung für viele.

Habe aktuell leider keine Zeit um das Modul zu testen, werde ich aber noch tuen.

Vorab:
  • Vielleich wäre noch ein Editor (als Anwendung) sinnvoll, um dateien in deinem Format zu lesen (DateiBaum) und zu bearbeiten (hinzufügen und entfernen) zu können, ohne PB zu nutzen.
  • Da du ja durch deine eigenen Befehle (BigFile::Image usw.) alle eingeladenen Resourcen "überwachst" wäre es ja sogar denkbar, dass das Modul (auf wunsch) selbst das geladene BigFile aktualisieren könnte, wenn BigFile::Close() aufgerufen wird und dateien auf dem Dateisystem geladen wurde, die nicht im BigFile sind?
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: Module BigFile

Beitrag von GPI »

STARGÅTE hat geschrieben:
  • Vielleich wäre noch ein Editor (als Anwendung) sinnvoll, um dateien in deinem Format zu lesen (DateiBaum) und zu bearbeiten (hinzufügen und entfernen) zu können, ohne PB zu nutzen.
  • Da du ja durch deine eigenen Befehle (BigFile::Image usw.) alle eingeladenen Resourcen "überwachst" wäre es ja sogar denkbar, dass das Modul (auf wunsch) selbst das geladene BigFile aktualisieren könnte, wenn BigFile::Close() aufgerufen wird und dateien auf dem Dateisystem geladen wurde, die nicht im BigFile sind?
Einen Editor möchte ich eigentlich nicht schreiben, das geht auch irgendwie an Sinn des ganzen vorbei. Sinnvoll ist eher ein Tool, das automatisch bei "Create Execute" aufgerufen wird und dann eine aktuelle BigFile anlegt. Hinzufügen von Dateien ginge noch relativ unproblematisch, Index-Daten auslesen, erweitern und aus der Datei löschen (befinden sich immer am schluß), neue gepackte Daten hinzufügen, den Pointer auf die Indexdaten am Anfang korrigieren und am ende die neuen gepackten Indexdaten speichern. Beim löschen wirds komplizierter, entweder man lässt die gepackte Datei in der BigFile und entfernt nur den Eintrag aus der Indexdatei oder man muss die Datei neu aufbauen, weil in der Mitte eine Lücke ist, die gefüllt werden muss.

Das mit den aktualisieren ist etwas problematisch. In der BigFile sind nur Dateiname, Pfad, gepackte Größe und ungepackte Größe gespeichert. Eine Prüfsumme gibts auch nicht (halte ich nicht für nötig, wenn das entpacken fehlschlägt, dann stimmt was nicht ;) ). Zu erkennen, ob die Datei aktuell ist, ist schwer. Neue Dateien zu finden dagegen jetzt nicht.
Aber eine fehlende Datei ist nicht zwingend ein Fehler.
Ich denke jetzt bspw. an ein Spiel mit Leveleditor. Die eingebauten Levels sind in der Bigfile, die von Benutzer erstellten außerhalb. Durch SetPath kann man das ganze so legen, dass das Spiel selbst nicht zwischen den beiden Arten von Levels unterscheiden muss, die Bigfile-Levels werden ja "virtuell" über die vorhandenen Userlevels gelegt und haben bei der Execute vorrang.

Wie oben geschrieben, mein Code verhält sich unterschiedlich, jenachdem ob der Debugger läuft oder nicht. Mit debugger geh ich davon aus, das man gerade in der Entwicklung ist und man schnell was testen will. Dann immer ein neues BigFile zu erstellen ist nicht gerade produktiv. Darum wird in diesen Fall nur auf die BigFile-Dateien zurückgegriffen, wenn sie nicht auf der HDD gefunden werden können. Das ist auch praktisch, wenn man bspw. einen Patch erstellen will. So verschiebt man alle Dateien, die schon in BigFile drin sind und hat nur noch die neuen "Patch"-Dateien vorliegen.

Wenn man ein bischen mit rumspielt wirds hoffentlich klarer :)
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: Module BigFile

Beitrag von GPI »

Ein kleines Tool:

Code: Alles auswählen

#bigfile_create=#True
XIncludeFile "..\!Module\bigfile.pbi"

EnableExplicit


Macro IfSet(a,b):a=b:If a:EndMacro: ;endindent ;endindent
Macro IfNotSet(a,b):a=b:If a=#False:EndMacro: ;endindent ;endindent
Macro RemoveBackslash(p):If Right(p,1)="\" : p=Left(p,Len(p)-1):EndIf:EndMacro


; "%PATH" "%PROJECT" "%EXECUTABLE"
Define path.s,project.s,executable.s
Define in
Define str.s
Define com.s
Define i
Define addpath.s


path=ProgramParameter()
project=ProgramParameter()
executable=ProgramParameter()

;MessageRequester("Test",path+Chr(10)+project+Chr(10)+executable+Chr(10))

OpenWindow(0,0,0,200,0,"Create Bigfile",#PB_Window_Minimize|#PB_Window_ScreenCentered)


If project<>""
  path=Project
EndIf

SetCurrentDirectory(Left(path,Len(path)-1))
Debug "Current:"+path
executable=GetPathPart(executable)

addpath="."

If FileSize(path+"bigfile.txt")>0
  IfSet(in,ReadFile(#PB_Any,path+"bigfile.txt"))
    While Not Eof(in)
      While WindowEvent():Delay(1):Wend
      str=Trim(ReadString(in))
      
      Select Left(str,1)
        Case ">"          
          com=Trim(Right(str,Len(str)-1))
          SetWindowTitle(0,"Create "+com)
          BigFileCreate::Close()
          BigFileCreate::Create(executable+com)
          addpath="."
          Debug "Create "+executable+com
        Case "<"
          com=Trim(Right(str,Len(str)-1))
          RemoveBackslash(com)
          If Left(com,1)="\":com=Right(com,Len(com)-1):EndIf
          If com="":com=".":EndIf
          addpath=com
        Case "|"
          com=Trim(Right(str,Len(str)-1))
          SetCurrentDirectory(com)
        Case "*" ; Comment
        Default
          If str
            i=FileSize(str)
            If i=-2
              Debug "adddir:"+str
              BigFileCreate::AddDir(str,addpath)
            ElseIf i>0
              Debug "Addfile:"+str
              BigFileCreate::AddFile(str,addpath)
            EndIf
          EndIf
      EndSelect
    Wend
    CloseFile(in)
    BigFileCreate::Close()
  EndIf
EndIf
CloseWindow(0)
          
Das Ding sollte nach einer Execute-Erstellung aufgerufen werden und folgende Parameter übergeben:
"%PATH" "%PROJECT" "%EXECUTABLE"
Es schaut dann in Projectpfad (oder falls nicht vorhanden, dort wo der Quellcode sich befindet) nach, ob es eine BigFile.txt gibt.
Aufbau ist ziemlich simpel, das erste Zeichen bestimmt, was gemacht wird
"*"
Kommentare beginnen einfach mit Stern
">"
Definiert den Dateinamen der BigFile. Sie wird in Verzeichnis der Executable erzeugt.
"<"
Der "Ordner" im BigFile.
"|"
Verändert den CurrentDirectory für die Quelldateien.

Ansonsten werden alle anderen Angaben als Dateien/Ordner interpretiert. Wenn ein Ordner hinzugefügt wird, dann wird nur der Inhalt und sämtliche Unterordner hinzugefügt.
Beispielsweise sieht dann so eine Bigfile.txt aus:

Code: Alles auswählen

*Testbigfile
>Testfile.big
*folgendes in Image des BigFile packen
<image
*Img=ordner - kompletter inhalt des Ordners hinzufügen (ohne img!)
img
*rootverzeichnis von bigfile
<
*Path ändern 
|E:\purebasic\Crillion\Exe\pack\Sets
*von dort eine Datei einpacken (landet in Root von Bigfile, auch wenn sie hier in pc\ sich befindet)
pc\Menu-Aktiv2.png

*neue bigfile
>TestFile2.big
C64
Einen schnellen Test, ob die Bigfile in Ordnung ist:

Code: Alles auswählen

XIncludeFile "..\!module\bigfile.pbi"

bigfile::Load("testfile.big")
bigfile::debuglist()
bigfile::clear()
debug "----"
bigfile::load("testfile2.big")
bigfile::debuglist()
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Benutzeravatar
_JON_
Beiträge: 389
Registriert: 30.03.2010 15:24

Re: Module BigFile

Beitrag von _JON_ »

Sieht auf jeden Fall mal sehr interessant aus. :allright:

Zur Zeit häng ich noch zu sehr an der LTS, aber werde es bestimmt bald gebrauchen können.
PureBasic 5.46 LTS (Windows x86/x64) | windows 10 x64 Oktober failure
Antworten