diff options
Diffstat (limited to '')
-rw-r--r-- | Game/Code/Classes/UCovers.pas | 539 |
1 files changed, 390 insertions, 149 deletions
diff --git a/Game/Code/Classes/UCovers.pas b/Game/Code/Classes/UCovers.pas index 1d3f737c..d3665854 100644 --- a/Game/Code/Classes/UCovers.pas +++ b/Game/Code/Classes/UCovers.pas @@ -1,5 +1,23 @@ unit UCovers; +{******************** + UCover + Contains Class managing Covers.Cache File + File Structure: + TCC_FileHeader + + TextureData + * Array of TCC_TextureData + + Indexes + * TCC_FileIndex Header Block + * TCC_FileIndex + * String containing Filename of Last IndexEntry. Ending with #0 + . + . + * TCC_FileIndex Footer Block +*********************} + interface {$IFDEF FPC} @@ -21,23 +39,81 @@ uses OpenGL12, UThemes, UTexture; +const + cCC_CoverW = 128; + cCC_CoverH = 128; + cCC_CoverSize = cCC_CoverW * cCC_CoverH * 3; + cCC_HeaderText = 'USDxCo' + #0 + #1; + cCC_HeaderVersion = 1000; + cCC_IndexIndicator= 'I' + 'N' + 'D' + #0; + type TCover = record Name: string; W: word; H: word; Size: integer; - Position: integer; // position of picture in the cache file + Position: integer; //position of picture in the cache file // Data: array of byte; end; + //------------------------------------------------------------------- + //Covers.Cache File Parts + + TCC_Hash = Array [1..32] of Char; + TCC_FileHeader = record + FileTyp: Array [1..8] of Char; //Some String to detect if this is the file we want to open + Version: DWord; //Version of Covers.Cache File + Hash: TCC_Hash; //Some Randomly Created Alphanumeric String to Identify w/ SongDB + CoverW: Word; //Width of all Covers in Cache + CoverH: Word; //Height of all Covers in Cache + DataStart: Cardinal; //Start Position in Bytes of Data Part + DataLength: Cardinal; //Start Position in Bytes of Data Part + IndexStart: Cardinal; //Start of Index Block in Bytes + end; + + PCC_TextureData = ^TCC_TextureData; + TCC_TextureData = Array [0..cCC_CoverSize - 1] of Byte; + + TCC_FileIndexHeader = record + Indicator: Array [1..4] of Char; //Contains INDE + HighestID: Cardinal; //Highest ID of a Cover + end; + + TCC_FileIndex = record + LastUpdated: Integer; //Time of LastFilechange + DataStart: Cardinal; //Position of TextureData of this Cover in Bytes. + //0 if this is empty slot(Deleted Cover)(Id not available) + //1 if the Texture Data is not already loaded to Cache + //High(Cardinal) if this is IndexFooter + //Filename: String; + end; + + TCC_IndexListItem = record + Filename: String; + TexID: Integer; + FileIndex: TCC_FileIndex; + end; + TCC_IndexList = Array of TCC_IndexListItem; + TCovers = class private - Filename: String; + Filename: String; + + Header: TCC_FileHeader; + HighestID: Integer; + Count: Cardinal; + Index: TCC_IndexList; + IndexNeedRewrite: Boolean; //Index in CacheFile is overwritten by other Data + CacheReadOnly: Boolean; //Cache File is read only - WritetoFile: Boolean; + Function WriteHeader:Boolean; + Function ReadHeader: Boolean; + Function ReadIndex: Boolean; + Function WriteIndex: Boolean; + Function AddTexData(Data: PCC_TextureData): Cardinal; public W: word; H: word; @@ -45,14 +121,15 @@ type Data: array of byte; Cover: array of TCover; - constructor Create; - procedure Load; - procedure Save; + property Hash: TCC_Hash read Header.Hash; + + constructor Create(const Filename: String); + procedure Load(const Filename: String); Function AddCover(FileName: string): Integer; //Returns ID, Checks Cover for Change, Updates Cover if required - function CoverExists(FileName: string): boolean; - function CoverNumber(FileName: string): integer; //Returns ID by FilePath + function CoverExists(FileName: string): Integer; //Returns ID by FilePath procedure PrepareData(FileName: string); Procedure LoadTextures; + Function ReWriteCache: Boolean; //Deletes old cover.cache file and writes new one end; var @@ -65,189 +142,353 @@ uses UMain, ULog, DateUtils; -constructor TCovers.Create; +constructor TCovers.Create(const Filename: String); begin - W := 128; - H := 128; - Size := W*H*3; - Load; - WritetoFile := True; + HighestID := -1; + SetLength(Index, HighestID + 2); + Load(Filename); end; -procedure TCovers.Load; +//---------------------------------------------- +// Some File handling helpers + +//-------- +// Reads and Checks Header. Returns True if Header is correct +//-------- +Function TCovers.ReadHeader: Boolean; var - F: File; - C: integer; // cover number - W: word; - H: word; - Bits: byte; - NLen: word; - Name: string; -// Data: array of byte; + F: File of TCC_FileHeader; begin - if FileExists(GamePath + 'covers.cache') then - begin - AssignFile(F, GamePath + 'covers.cache'); - Reset(F, 1); - - WritetoFile := not FileIsReadOnly(GamePath + 'covers.cache'); - - SetLength(Cover, 0); + try + //Read Header + AssignFile(F, Filename); + + try + Reset(F); + Read(F, Header); + finally + CloseFile(F); + end; - while not EOF(F) do + //Check Header + If (Header.FileTyp = cCC_HeaderText) AND + (Header.Version = cCC_HeaderVersion) then begin - SetLength(Cover, Length(Cover)+1); - - BlockRead(F, W, 2); - Cover[High(Cover)].W := W; - - BlockRead(F, H, 2); - Cover[High(Cover)].H := H; + Result := True; + IndexNeedRewrite := True; + end + Else + Result := False; - BlockRead(F, Bits, 1); - Cover[High(Cover)].Size := W * H * (Bits div 8); - - // test - // W := 128; - // H := 128; - // Bits := 24; - // Seek(F, FilePos(F) + 3); + except + Result := False; + end; +end; - BlockRead(F, NLen, 2); - SetLength(Name, NLen); +//-------- +// Writes Header(Resets File). Returns True if Writing succeed +//-------- +Function TCovers.WriteHeader:Boolean; +var + F: File of TCC_FileHeader; +begin + try + Result := True; + //Read Header + AssignFile(F, Filename); + try + If (not FileExists(Filename)) then + ReWrite(F) + else + Reset(F); + + Write(F, Header); + finally + CloseFile(F); + end; + except + Result := False; + end; +end; - BlockRead(F, Name[1], NLen); - Cover[High(Cover)].Name := Name; +//-------- +// Reads and Checks Index. Returns True if Index is correct +//-------- +Function TCovers.ReadIndex: Boolean; +var + F: File of Byte; + IndexHeader: TCC_FileIndexHeader; + I: Integer; - Cover[High(Cover)].Position := FilePos(F); - Seek(F, FilePos(F) + W*H*(Bits div 8)); + Procedure mReadLn(var S: String); + var J: Integer; + begin + S := ''; + J := 0; + + Repeat + Inc(J); + SetLength(S, J); + Read(F, Byte(S[J])); + Until S[J] = #10 + end; +begin + try + //Read Header + AssignFile(F, Filename); + try + Reset(F); + Seek(F, Header.IndexStart); + + BlockRead(F, IndexHeader, SizeOf(TCC_FileIndexHeader)); + + If (IndexHeader.Indicator = cCC_IndexIndicator) then + begin + Result := False; + + HighestID := IndexHeader.HighestID; + SetLength(Index, HighestID + 2); + + Count := 0; + Result := True; + If (HighestID > 0) then + begin + //Read File Infos until (Eof or Footer) + I := 0; + While (Not Eof(F)) AND ((I <= 0) OR (Index[I].FileIndex.DataStart <> High(Cardinal))) do + begin + If (I > HighestID) then + begin //Header IndexCOunt was wrong, running out of array + Inc(HighestID); + IndexNeedReWrite := True; + SetLength(Index, HighestID + 2); + end; + + BlockRead(F, Index[I].FileIndex, SizeOf(TCC_FileIndex)); + Index[I].TexID := -1; + + If (Index[I].FileIndex.DataStart <> High(Cardinal)) AND (Not Eof(F)) then + begin + //Read Filename + mReadLn(Index[I].Filename); + Inc(Count); + end; + + Inc(I); + end; + + If (Index[HighestID + 1].FileIndex.DataStart = High(Cardinal)) then + begin //No Footer found + IndexNeedReWrite := True; + end; + end; + + end; + + finally + CloseFile(F); + end; + except + Result := False; + end; +end; - // SetLength(Cover[High(Cover)].Data, W*H*(Bits div 8)); - // BlockRead(F, Cover[High(Cover)].Data[0], W*H*(Bits div 8)); +//-------- +// Writes Index. Returns True if Writing succeed +//-------- +Function TCovers.WriteIndex: Boolean; +var + F: File of Byte; + IndexHeader: TCC_FileIndexHeader; + I: Integer; - end; // While + Procedure mWriteLn(var S: String); + var N: Byte; + begin + BlockWrite(F, S, Length(S)); + N := Byte(#10); + Write(F, N); + end; +begin + Result := WriteHeader; - CloseFile(F); - end; // fileexists + If (Result) then + begin + try + //Read Header + AssignFile(F, Filename); + try + Reset(F); + + Seek(F, Header.IndexStart); + //Write Header + IndexHeader.Indicator := cCC_IndexIndicator; + IndexHeader.HighestID := HighestID; + + BlockWrite(F, IndexHeader, SizeOf(TCC_FileIndexHeader)); + + Count := 0; + Result := True; + + //Prepare Footer + Index[HighestID + 1].FileIndex.DataStart := High(Cardinal); + + // Write Fileinfo + For I := 0 to HighestID+1 do + begin + BlockWrite(F, Index[I].FileIndex, SizeOf(TCC_FileIndex)); + mWriteLn(Index[I].Filename); + end; + + finally + CloseFile(F); + end; + except + Result := False; + end; + end; end; -procedure TCovers.Save; +//-------- +// Writes some Texture Data to the End of TextureData Block +//-------- +Function TCovers.AddTexData(Data: PCC_TextureData): Cardinal; var - F: File; - C: integer; // cover number - W: word; - H: word; - NLen: word; - Bits: byte; + F: File of Byte; begin -{ AssignFile(F, GamePath + 'covers.cache'); - Rewrite(F, 1); - - Bits := 24; - for C := 0 to High(Cover) do begin - W := Cover[C].W; - H := Cover[C].H; - - BlockWrite(F, W, 2); - BlockWrite(F, H, 2); - BlockWrite(F, Bits, 1); - - NLen := Length(Cover[C].Name); - BlockWrite(F, NLen, 2); - BlockWrite(F, Cover[C].Name[1], NLen); - BlockWrite(F, Cover[C].Data[0], W*H*(Bits div 8)); - end; + try + AssignFile(F, Filename); + try + Reset(F); + Seek(F, Header.DataStart + Header.DataLength); + + BlockWrite(F, Data, SizeOf(TCC_TextureData)); - CloseFile(F);} + Result := Header.DataStart + Header.DataLength; + Inc(Header.DataLength, SizeOf(TCC_TextureData)); + + Header.IndexStart := Header.DataStart + Header.DataLength + 1; + IndexNeedReWrite := True; + finally + CloseFile(F); + end; + except + Result := 0; + end; end; -Function TCovers.AddCover(FileName: string): Integer; +procedure TCovers.Load(const Filename: String); var - B: integer; - F: File; - C: integer; // cover number - NLen: word; - Bits: byte; + Succeed: Boolean; begin - if not CoverExists(FileName) then + Self.Filename := Filename; + Succeed := False; + If (FileExists(Filename)) then begin - SetLength(Cover, Length(Cover)+1); - Cover[High(Cover)].Name := FileName; - - Cover[High(Cover)].W := W; - Cover[High(Cover)].H := H; - Cover[High(Cover)].Size := Size; - - // do not copy data. write them directly to file -// SetLength(Cover[High(Cover)].Data, Size); -// for B := 0 to Size-1 do -// Cover[High(Cover)].Data[B] := CacheMipmap[B]; - - if WritetoFile then - begin - AssignFile(F, GamePath + 'covers.cache'); - - if FileExists(GamePath + 'covers.cache') then - begin - Reset(F, 1); - Seek(F, FileSize(F)); - end - else + CacheReadOnly := FileisReadOnly(Filename); + If (ReadHeader) then + begin //Header successful read + If (ReadIndex) then begin - Rewrite(F, 1); + Succeed := True; end; + end; + end; - Bits := 24; - - BlockWrite(F, W, 2); - BlockWrite(F, H, 2); - BlockWrite(F, Bits, 1); - - NLen := Length(FileName); - BlockWrite(F, NLen, 2); - BlockWrite(F, FileName[1], NLen); + If not Succeed and not CacheReadOnly then + If not (ReWriteCache) then + CacheReadOnly := True; +end; - Cover[High(Cover)].Position := FilePos(F); - BlockWrite(F, CacheMipmap[0], W*H*(Bits div 8)); +Function TCovers.AddCover(FileName: string): Integer; +var I: Integer; +begin + Result := CoverExists(Filename); + If (Result = -1) then + begin //Add Cover(Does not exist) + If (Count <= HighestID) then + begin //There is an empty slot, Search It + For I := 0 to HighestID do + If (Index[I].FileIndex.DataStart = 0) then + begin //Found the Slot + Result := I; + Break; + end; + end; - CloseFile(F); + If (Result = -1) then + begin //Attach it to the End + Inc(HighestID); + SetLength(Index, HighestID + 2); + Result := HighestID; end; + + Index[Result].Filename := Filename; + Index[Result].TexID := -1; + Index[Result].FileIndex.DataStart := 1; end else - Cover[High(Cover)].Position := 0; + begin //Check if File has Changed + If (Index[Result].FileIndex.LastUpdated < 0) then + begin + + end; + end; end; -function TCovers.CoverExists(FileName: string): boolean; +Function TCovers.CoverExists(FileName: string): integer; var - C: integer; // cover + I: integer; begin - Result := false; - C := 0; - - while (C <= High(Cover)) and (Result = false) do + Result := -1; + {$IFDEF MSWINDOWS} + Filename := Uppercase(Filename); + {$ENDIF} + + For I := 0 to HighestID do begin - if Cover[C].Name = FileName then - Result := true; - - Inc(C); + If (Index[I].FileIndex.DataStart <> 0) AND + {$IFDEF MSWINDOWS} + (Uppercase(Index[I].Filename) = Filename) then + {$ELSE} + (Index[I].Filename = Filename) + {$ENDIF} + begin + Result := I; + Exit; + end; end; end; -function TCovers.CoverNumber(FileName: string): integer; -var - C: integer; -begin - Result := -1; - C := 0; - - while (C <= High(Cover)) and (Result = -1) do +//-------- +// Deletes old cover.cache file and writes new one +//-------- +Function TCovers.ReWriteCache: Boolean; + + Function MakeHash: TCC_Hash; + const AlphaNumeric: Array[0..35] of Char = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); + var I: Integer; begin - if Cover[C].Name = FileName then - Result := C; - - Inc(C); + For I := Low(Result) to High(Result) do + Result[I] := AlphaNumeric[Random(Length(AlphaNumeric))]; end; +begin + If not CacheReadOnly then + begin + Header.FileTyp := cCC_HeaderText; + Header.Version := cCC_HeaderVersion; + Header.Hash := MakeHash; + Header.CoverW := cCC_CoverW; + Header.CoverH := cCC_CoverH; + Header.DataStart := SizeOf(TCC_FileHeader) + 4; //Total of 64 Bytes (4 Bytes Space) + Header.DataLength := 0; + Header.IndexStart := Header.DataStart + Header.DataLength + 4; + + Result := WriteIndex; + end + else + Result := False; end; procedure TCovers.PrepareData(FileName: string); @@ -260,7 +501,7 @@ begin AssignFile(F, GamePath + 'covers.cache'); Reset(F, 1); - C := CoverNumber(FileName); + C := CoverExists(FileName); SetLength(Data, Cover[C].Size); if Length(Data) < 6 then Log.LogStatus('Length(Data) < 6', 'TCovers.PrepareData'); |