{ This file is part of the Free Pascal Integrated Development Environment Copyright (c) 1998 by Berczi Gabor Resource File support objects and routines See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit WResourc; {$ifdef cpullvm} {$modeswitch nestedprocvars} {$endif} interface uses Objects; const TPDataBlockSignature = ord('F')+ord('B')*256; ResourceBlockSignature = ord('R')+ord('D')*256; langDefault = 0; rcBinary = 1; type TResourceEntryHeader = packed record ID : longint; LangID : longint; Flags : longint; DataOfs: longint; DataLen: sw_word; end; TResourceHeader = packed record _Class : longint; Flags : longint; NameLen : word; EntryCount : word; end; TResourceFileHeader = packed record Signature : word; InfoType : word; InfoSize : longint; { ---- } TableOfs : longint; end; PResourceEntry = ^TResourceEntry; TResourceEntry = object(TObject) constructor Init(AID, ALangID, AFlags, ADataLen: longint); private ID : longint; LangID : longint; Flags : longint; DataOfs : longint; DataLen : sw_word; procedure BuildHeader(var Header : TResourceEntryHeader); end; PResourceEntryCollection = ^TResourceEntryCollection; TResourceEntryCollection = object(TSortedCollection) function At(Index: Sw_Integer): PResourceEntry; function Compare(Key1, Key2: Pointer): Sw_Integer; virtual; function SearchEntryForLang(ALangID: longint): PResourceEntry; end; PGlobalResourceEntryCollection = ^TGlobalResourceEntryCollection; TGlobalResourceEntryCollection = object(TSortedCollection) function At(Index: Sw_Integer): PResourceEntry; function Compare(Key1, Key2: Pointer): Sw_Integer; virtual; end; PResource = ^TResource; TResource = object(TObject) constructor Init(const AName: string; AClass, AFlags: longint); function GetName: string; virtual; function FirstThatEntry(Func: TCallbackFunBoolParam): PResourceEntry; virtual; procedure ForEachEntry(Func: TCallbackProcParam); virtual; destructor Done; virtual; private Name : PString; _Class : longint; Flags : longint; Items : PResourceEntryCollection; procedure BuildHeader(var Header : TResourceHeader); end; TResourceCollection = object(TSortedCollection) function At(Index: Sw_Integer): PResource; function Compare(Key1, Key2: Pointer): Sw_Integer; virtual; function SearchResourceByName(const AName: string): PResource; end; PResourceCollection = ^TResourceCollection; TResourceFile = object(TObject) constructor Init(var RS: TStream; ALoad: boolean); constructor Create(var RS: TStream); constructor Load(var RS: TStream); constructor CreateFile(AFileName: string); constructor LoadFile(AFileName: string); function FirstThatResource(Func: TCallbackFunBoolParam): PResource; virtual; procedure ForEachResource(Func: TCallbackProcParam); virtual; procedure ForEachResourceEntry(Func: TCallbackProcParam); virtual; function CreateResource(const Name: string; AClass, AFlags: longint): boolean; virtual; function AddResourceEntry(const ResName: string; ALangID, AFlags: longint; var Data; ADataSize: sw_integer): boolean; virtual; function AddResourceEntryFromStream(const ResName: string; ALangID, AFlags: longint; var Source: TStream; ADataSize: longint): boolean; virtual; function DeleteResourceEntry(const ResName: string; ALangID: longint): boolean; virtual; function DeleteResource(const ResName: string): boolean; virtual; function ReadResourceEntry(const ResName: string; ALangID: longint; var Buf; BufSize: sw_word): boolean; function ReadResourceEntryToStream(const ResName: string; ALangID: longint; var DestS: TStream): boolean; procedure Flush; virtual; destructor Done; virtual; public BaseOfs: longint; function FindResource(const ResName: string): PResource; function FindResourceEntry(const ResName: string; ALangID: longint): PResourceEntry; private S : PStream; MyStream : boolean; Resources : PResourceCollection; Entries : PGlobalResourceEntryCollection; Header : TResourceFileHeader; Modified : boolean; procedure UpdateBlockDatas; function GetNextEntryID: longint; function GetTotalSize(IncludeHeaders: boolean): longint; function CalcSizes(IncludeHeaders, UpdatePosData: boolean): longint; procedure AddResEntryPtr(P: PResource; E: PResourceEntry); procedure RemoveResEntryPtr(P: PResource; E: PResourceEntry); function DeleteArea(AreaStart, AreaSize, TotalSize: longint): boolean; procedure BuildFileHeader; procedure WriteHeader; procedure WriteResourceTable; end; PResourceFile = ^TResourceFile; implementation uses WUtils; function TResourceEntryCollection.At(Index: Sw_Integer): PResourceEntry; begin At:=inherited At(Index); end; function TResourceEntryCollection.Compare(Key1, Key2: Pointer): Sw_Integer; var K1: PResourceEntry absolute Key1; K2: PResourceEntry absolute Key2; Re: Sw_integer; begin if K1^.LangIDK2^.LangID then Re:= 1 else Re:=0; Compare:=Re; end; function TResourceEntryCollection.SearchEntryForLang(ALangID: longint): PResourceEntry; var P: PResourceEntry; E: TResourceEntry; Index: sw_integer; begin E.LangID:=ALangID; if Search(@E,Index)=false then P:=nil else P:=At(Index); SearchEntryForLang:=P; end; function TGlobalResourceEntryCollection.At(Index: Sw_Integer): PResourceEntry; begin At:=inherited At(Index); end; function TGlobalResourceEntryCollection.Compare(Key1, Key2: Pointer): Sw_Integer; var K1: PResourceEntry absolute Key1; K2: PResourceEntry absolute Key2; Re: Sw_integer; begin if K1^.IDK2^.ID then Re:= 1 else Re:=0; Compare:=Re; end; constructor TResourceEntry.Init(AID, ALangID, AFlags, ADataLen: longint); begin inherited Init; ID:=AID; LangID:=ALangID; Flags:=AFlags; DataLen:=ADataLen; end; procedure TResourceEntry.BuildHeader(var Header : TResourceEntryHeader); begin FillChar(Header,SizeOf(Header),0); Header.ID:=ID; Header.LangID:=LangID; Header.Flags:=Flags; Header.DataLen:=DataLen; Header.DataOfs:=DataOfs; end; constructor TResource.Init(const AName: string; AClass, AFlags: longint); begin inherited Init; Name:=NewStr(AName); _Class:=AClass; Flags:=AFlags; New(Items, Init(10,50)); end; function TResource.GetName: string; begin GetName:=GetStr(Name); end; function TResource.FirstThatEntry(Func: TCallbackFunBoolParam): PResourceEntry; var EP,P: PResourceEntry; I: sw_integer; begin P:=nil; for I:=0 to Items^.Count-1 do begin EP:=Items^.At(I); if Byte(Longint(CallPointerMethodLocal(Func, get_caller_frame(get_frame,get_pc_addr),@Self,EP)))<>0 then begin P := EP; Break; end; end; FirstThatEntry:=P; end; procedure TResource.ForEachEntry(Func: TCallbackProcParam); var RP: PResourceEntry; I: sw_integer; begin for I:=0 to Items^.Count-1 do begin RP:=Items^.At(I); CallPointerMethodLocal(Func, get_caller_frame(get_frame,get_pc_addr),@Self,RP); end; end; procedure TResource.BuildHeader(var Header : TResourceHeader); begin FillChar(Header,SizeOf(Header),0); Header._Class:=_Class; Header.Flags:=Flags; Header.NameLen:=length(GetName); Header.EntryCount:=Items^.Count; end; destructor TResource.Done; begin inherited Done; if Name<>nil then DisposeStr(Name); Name:=nil; if Items<>nil then Dispose(Items, Done); Items:=nil; end; function TResourceCollection.At(Index: Sw_Integer): PResource; begin At:=inherited At(Index); end; function TResourceCollection.Compare(Key1, Key2: Pointer): Sw_Integer; var K1: PResource absolute Key1; K2: PResource absolute Key2; N1,N2: string; Re: Sw_integer; begin N1:=UpcaseStr(K1^.GetName); N2:=UpcaseStr(K2^.GetName); if N1N2 then Re:= 1 else Re:=0; Compare:=Re; end; function TResourceCollection.SearchResourceByName(const AName: string): PResource; var P,R: PResource; Index: sw_integer; begin New(R, Init(AName,0,0)); if Search(R,Index)=false then P:=nil else P:=At(Index); Dispose(R, Done); SearchResourceByName:=P; end; constructor TResourceFile.Create(var RS: TStream); begin if Init(RS,false)=false then Fail; end; constructor TResourceFile.Load(var RS: TStream); begin if Init(RS,true)=false then Fail; end; constructor TResourceFile.Init(var RS: TStream; ALoad: boolean); var OK: boolean; RH: TResourceHeader; REH: TResourceEntryHeader; EndPos,I: longint; P: PResource; E: PResourceEntry; St: string; begin inherited Init; S:=@RS; New(Resources, Init(100, 1000)); New(Entries, Init(500,2000)); OK:=true; if ALoad=false then Modified:=true else begin S^.Reset; BaseOfs:=S^.GetPos; S^.Read(Header,SizeOf(Header)); OK:=(S^.Status=stOK) and (Header.Signature=TPDataBlockSignature) and (Header.InfoType=ResourceBlockSignature); if OK then begin S^.Seek(BaseOfs+Header.TableOfs); OK:=S^.Status=stOK; end; EndPos:=BaseOfs+Header.InfoSize; if OK then while OK and (S^.GetPos0 then begin P := RP; Break; end; end; FirstThatResource:=P; end; procedure TResourceFile.ForEachResource(Func: TCallbackProcParam); var RP: PResource; I: sw_integer; begin for I:=0 to Resources^.Count-1 do begin RP:=Resources^.At(I); CallPointerMethodLocal(Func,get_caller_frame(get_frame,get_pc_addr),@Self,RP); end; end; procedure TResourceFile.ForEachResourceEntry(Func: TCallbackProcParam); var E: PResourceEntry; I: sw_integer; begin for I:=0 to Entries^.Count-1 do begin E:=Entries^.At(I); CallPointerMethodLocal(Func,get_caller_frame(get_frame,get_pc_addr),@Self,E); end; end; function TResourceFile.CreateResource(const Name: string; AClass, AFlags: longint): boolean; var OK: boolean; P: PResource; begin OK:=FindResource(Name)=nil; if OK then begin New(P, Init(Name,AClass,AFlags)); Resources^.Insert(P); Modified:=true; end; CreateResource:=OK; end; function TResourceFile.AddResourceEntry(const ResName: string; ALangID, AFlags: longint; var Data; ADataSize: sw_integer): boolean; const BlockSize = 4096; var OK: boolean; P: PResource; E: PResourceEntry; RemSize,CurOfs,FragSize: longint; begin P:=FindResource(ResName); OK:=P<>nil; if OK then OK:=(P^.Items^.SearchEntryForLang(ALangID)=nil); if OK then begin New(E, Init(GetNextEntryID,ALangID, AFlags, ADataSize)); AddResEntryPtr(P,E); UpdateBlockDatas; RemSize:=ADataSize; CurOfs:=0; S^.Reset; S^.Seek(BaseOfs+E^.DataOfs); while (RemSize>0) do begin FragSize:=Min(RemSize,BlockSize); S^.Write(PByteArray(@Data)^[CurOfs],FragSize); Dec(RemSize,FragSize); Inc(CurOfs,FragSize); end; Modified:=true; end; AddResourceEntry:=OK; end; function TResourceFile.AddResourceEntryFromStream(const ResName: string; ALangID, AFlags: longint; var Source: TStream; ADataSize: longint): boolean; const BufSize = 4096; var OK: boolean; P: PResource; E: PResourceEntry; RemSize,FragSize: longint; Buf: pointer; begin P:=FindResource(ResName); OK:=P<>nil; if OK then OK:=(P^.Items^.SearchEntryForLang(ALangID)=nil); if OK then begin New(E, Init(GetNextEntryID, ALangID, AFlags, ADataSize)); AddResEntryPtr(P,E); UpdateBlockDatas; GetMem(Buf,BufSize); RemSize:=ADataSize; S^.Reset; S^.Seek(BaseOfs+E^.DataOfs); while (RemSize>0) do begin FragSize:=Min(RemSize,BufSize); Source.Read(Buf^,FragSize); S^.Write(Buf^,FragSize); Dec(RemSize,FragSize); end; FreeMem(Buf,BufSize); Modified:=true; end; AddResourceEntryFromStream:=OK; end; function TResourceFile.DeleteResourceEntry(const ResName: string; ALangID: longint): boolean; var E: PResourceEntry; P: PResource; OK: boolean; begin P:=FindResource(ResName); OK:=P<>nil; if OK then E:=P^.Items^.SearchEntryForLang(ALangID); OK:=OK and (E<>nil); if OK then begin OK:=DeleteArea(E^.DataOfs,E^.DataLen,GetTotalSize(false)); if OK then begin RemoveResEntryPtr(P,E); Dispose(E, Done); end; Modified:=true; end; DeleteResourceEntry:=OK; end; function TResourceFile.DeleteResource(const ResName: string): boolean; var P: PResource; E: PResourceEntry; OK: boolean; begin P:=FindResource(ResName); OK:=P<>nil; if P<>nil then begin while OK and (P^.Items^.Count>0) do begin E:=P^.Items^.At(P^.Items^.Count-1); OK:=OK and DeleteResourceEntry(ResName,E^.LangID); end; Modified:=true; end; if OK then Resources^.Free(P); DeleteResource:=OK; end; function TResourceFile.ReadResourceEntry(const ResName: string; ALangID: longint; var Buf; BufSize: sw_word): boolean; var E: PResourceEntry; P: PResource; OK: boolean; CurOfs,CurFrag: sw_word; TempBuf: pointer; const TempBufSize = 4096; begin E:=nil; P:=FindResource(ResName); OK:=P<>nil; if OK then E:=P^.Items^.SearchEntryForLang(ALangID); OK:=OK and (E<>nil); OK:=OK and (E^.DataLen<=BufSize); if OK then begin GetMem(TempBuf,TempBufSize); S^.Reset; S^.Seek(BaseOfs+E^.DataOfs); OK:=(S^.Status=stOK); CurOfs:=0; while OK and (CurOfsnil; if OK then E:=P^.Items^.SearchEntryForLang(ALangID); OK:=OK and (E<>nil); if OK then begin GetMem(TempBuf,TempBufSize); S^.Reset; S^.Seek(BaseOfs+E^.DataOfs); OK:=(S^.Status=stOK); CurOfs:=0; { this results sometimes in endless loops when the resource are changed PM } if E^.DataLen<0 then OK:=false; while OK and (CurOfsnil then E:=P^.Items^.SearchEntryForLang(ALangID); FindResourceEntry:=E; end; procedure TResourceFile.Flush; begin if Modified=false then Exit; BuildFileHeader; S^.Seek(BaseOfs); WriteHeader; S^.Seek(BaseOfs+Header.TableOfs); WriteResourceTable; S^.Truncate; Modified:=false; end; procedure TResourceFile.BuildFileHeader; begin FillChar(Header,SizeOf(Header),0); with Header do begin Signature:=TPDataBlockSignature; InfoType:=ResourceBlockSignature; InfoSize:=GetTotalSize(true); TableOfs:=GetTotalSize(false); end; end; procedure TResourceFile.WriteHeader; begin S^.Write(Header,SizeOf(Header)); end; procedure TResourceFile.WriteResourceTable; var RH: TResourceHeader; REH: TResourceEntryHeader; procedure WriteResource(P: PResource); procedure WriteResourceEntry(P: PResourceEntry); begin P^.BuildHeader(REH); S^.Write(REH,SizeOf(REH)); end; var N: string; begin if P^.Items^.Count=0 then Exit; { do not store resources with no entries } P^.BuildHeader(RH); S^.Write(RH,SizeOf(RH)); N:=P^.GetName; S^.Write(N[1],length(N)); P^.ForEachEntry(TCallbackProcParam(@WriteResourceEntry)); end; begin ForEachResource(TCallbackProcParam(@WriteResource)); end; procedure TResourceFile.UpdateBlockDatas; begin CalcSizes(false,true); end; function TResourceFile.GetTotalSize(IncludeHeaders: boolean): longint; begin GetTotalSize:=CalcSizes(IncludeHeaders,false); end; function TResourceFile.CalcSizes(IncludeHeaders, UpdatePosData: boolean): longint; var RH : TResourceHeader; REH : TResourceEntryHeader; Size: longint; NamesSize: longint; procedure AddResourceEntrySize(P: PResourceEntry); begin if UpdatePosData then P^.DataOfs:=Size; P^.BuildHeader(REH); Inc(Size,REH.DataLen); end; procedure AddResourceSize(P: PResource); var RH: TResourceHeader; begin P^.BuildHeader(RH); Inc(NamesSize,RH.NameLen); end; begin Size:=0; NamesSize:=0; Inc(Size,SizeOf(Header)); { this is on start so we always include it } ForEachResourceEntry(TCallbackProcParam(@AddResourceEntrySize)); if IncludeHeaders then begin ForEachResource(TCallbackProcParam(@AddResourceSize)); Inc(Size,SizeOf(RH)*Resources^.Count); Inc(Size,SizeOf(REH)*Entries^.Count); Inc(Size,NamesSize); end; CalcSizes:=Size; end; function TResourceFile.DeleteArea(AreaStart, AreaSize, TotalSize: longint): boolean; const BufSize = 4096; var RemSize,FragSize,CurOfs: longint; Buf: pointer; OK: boolean; begin GetMem(Buf,BufSize); RemSize:=TotalSize-(AreaStart+AreaSize); CurOfs:=0; OK:=RemSize>=0; while (RemSize>0) do begin FragSize:=Min(RemSize,BufSize); S^.Seek(BaseOfs+AreaStart+AreaSize+CurOfs); S^.Read(Buf^,BufSize); OK:=OK and (S^.Status=stOK); if OK then begin S^.Seek(BaseOfs+AreaStart+CurOfs); S^.Write(Buf^,BufSize); OK:=OK and (S^.Status=stOK); end; Inc(CurOfs,FragSize); Dec(RemSize,FragSize); end; FreeMem(Buf,BufSize); DeleteArea:=OK; end; procedure TResourceFile.AddResEntryPtr(P: PResource; E: PResourceEntry); begin if (P=nil) or (E=nil) then Exit; P^.Items^.Insert(E); Entries^.Insert(E); end; procedure TResourceFile.RemoveResEntryPtr(P: PResource; E: PResourceEntry); begin if (P=nil) or (E=nil) then Exit; Entries^.Delete(E); P^.Items^.Delete(E); end; function TResourceFile.GetNextEntryID: longint; var ID: longint; begin if Entries^.Count=0 then ID:=1 else ID:=Entries^.At(Entries^.Count-1)^.ID+1; GetNextEntryID:=ID; end; destructor TResourceFile.Done; begin Flush; inherited Done; { if assigned(S) then dispose(S,Done); S:=nil;} if Resources<>nil then Dispose(Resources, Done); Resources:=nil; if Entries<>nil then begin Entries^.DeleteAll; Dispose(Entries, Done); Entries:=nil; end; if MyStream and Assigned(S) then Dispose(S, Done); end; constructor TResourceFile.CreateFile(AFileName: string); var B: PFastBufStream; begin New(B, Init(AFileName, stCreate, 4096)); if (B<>nil) and (B^.Status<>stOK) then begin Dispose(B, Done); B:=nil; end; if B=nil then Fail; if Create(B^)=false then Begin Dispose(B,Done); Fail; End; MyStream:=true; {$ifdef HASAMIGA} Flush; {$endif} end; constructor TResourceFile.LoadFile(AFileName: string); var B: PFastBufStream; begin New(B, Init(AFileName, stOpen, 4096)); if (B<>nil) and (B^.Status<>stOK) then begin Dispose(B, Done); B:=nil; end; if B=nil then Fail; if Load(B^)=false then Begin Dispose(B,Done); Fail; End; MyStream:=true; end; END.