{ This file is part of the Free Pascal run time library. Copyright (c) 2008 by Giulio Bernardi Resource support as external files 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. **********************************************************************} { This file implements two kinds of external resource support: - one for systems that support the mmap call (usually unix-like oses) - one fallback implementation based on pascal files and GetMem/FreeMem Be sure to define EXTRES_MMAP or EXTRES_GENERIC before including this file! } {$IF defined(EXTRES_MMAP) and defined(EXTRES_GENERIC)} {$FATAL EXTRES_MMAP and EXTRES_GENERIC can't be defined together} {$ENDIF} {$IF (not defined(EXTRES_MMAP)) and (not defined(EXTRES_GENERIC))} {$FATAL EXTRES_MMAP or EXTRES_GENERIC must be defined} {$ENDIF} const FPCRES_MAGIC = 'FPCRES'; FPCRES_VERSION = 1; {$IFDEF ENDIAN_BIG} FPCRES_ENDIAN = 1; {$ENDIF} {$IFDEF ENDIAN_LITTLE} FPCRES_ENDIAN = 2; {$ENDIF} FPCRES_EXT = '.fpcres'; type TExtHeader = packed record magic : array[0..5] of char;//'FPCRES' version : byte; //EXT_CURRENT_VERSION endianess : byte; //EXT_ENDIAN_BIG or EXT_ENDIAN_LITTLE count : longword; //resource count nodesize : longword; //size of header (up to string table, excluded) hdrsize : longword; //size of header (up to string table, included) reserved1 : longword; reserved2 : longword; reserved3 : longword; end; PExtHeader = ^TExtHeader; TResInfoNode = packed record nameid : longword; //name offset / integer ID / languageID ncounthandle : longword; //named sub-entries count/resource handle idcountsize : longword; //id sub-entries count / resource size subptr : longword; //first sub-entry offset end; PResInfoNode = ^TResInfoNode; {$IFDEF EXTRES_GENERIC} TResHandle = record info : PResInfoNode; ptr : Pointer; end; PResHandle = ^TResHandle; {$ENDIF} var ResHeader : PExtHeader = nil; usedhandles : longword = 0; {$IFDEF EXTRES_MMAP} fd : integer; fd_size : longword; reshandles : PPointer = nil; {$ENDIF} {$IFDEF EXTRES_GENERIC} fd : file; reshandles : PResHandle = nil; {$ENDIF} (***************************************************************************** Private Helper Functions *****************************************************************************) //resource functions are case insensitive... copied from genstr.inc function ResStrIComp(Str1, Str2 : PChar): SizeInt; var counter: SizeInt; c1, c2: char; begin counter := 0; c1 := upcase(str1[counter]); c2 := upcase(str2[counter]); while c1 = c2 do begin if (c1 = #0) or (c2 = #0) then break; inc(counter); c1 := upcase(str1[counter]); c2 := upcase(str2[counter]); end; ResStrIComp := ord(c1) - ord(c2); end; {!fixme!} //function InternalIsIntResource(aStr : pchar; out aInt : PtrUint) : boolean; function InternalIsIntResource(aStr : pchar; var aInt : PtrUint) : boolean; var i : integer; s : shortstring; code : word; begin InternalIsIntResource:=((PtrUInt(aStr) shr 16)=0); if InternalIsIntResource then aInt:=PtrUInt(aStr) else begin //a string like #number specifies an integer id if aStr[0]='#' then begin i:=1; while aStr[i]<>#0 do inc(i); if i>256 then i:=256; s[0]:=chr(i-1); Move(aStr[1],s[1],i-1); Val(s,aInt,code); InternalIsIntResource:=code=0; end; end; end; function GetResInfoPtr(const offset : longword) : PResInfoNode; inline; begin GetResInfoPtr:=PResInfoNode(PtrUInt(ResHeader)+offset); end; function GetPchar(const offset : longword) : Pchar; inline; begin GetPchar:=Pchar(PtrUInt(ResHeader)+offset); end; function GetPtr(const offset : longword) : Pointer; inline; begin GetPtr:=Pointer(PtrUInt(ResHeader)+offset); end; procedure FixResEndian; var ptr : plongword; blockend : plongword; begin //all info nodes reside in a contiguos block of memory. //they are all 16 bytes long and made by longwords //so, simply swap each longword in the block ptr:=GetPtr(sizeof(TExtHeader)); blockend:=GetPtr(ResHeader^.nodesize); while ptr0) and (not (pathstr[i] in ['.',DirectorySeparator])) do dec(i); if (i>0) and (pathstr[i]='.') then dec(i) else i:=len; pathstr[0]:=Chr(i); pathstr:=pathstr+FPCRES_EXT; len:=byte(pathstr[0]); GetExtResPath:=GetMem(len+1); Move(pathstr[1],GetExtResPath[0],len); GetExtResPath[len]:=#0; //writeln('Resource file is ',GetExtResPath); end; function BinSearchStr(arr : PResInfoNode; query : pchar; left, right : integer) : PResInfoNode; var pivot, res : integer; resstr : pchar; begin BinSearchStr:=nil; while left<=right do begin pivot:=(left+right) div 2; resstr:=GetPchar(arr[pivot].nameid); res:=ResStrIComp(resstr,query); if res<0 then left:=pivot+1 else if res>0 then right:=pivot-1 else begin BinSearchStr:=@arr[pivot]; exit; end; end; end; function BinSearchInt(arr : PResInfoNode; query : pchar; left, right : integer) : PResInfoNode; var pivot : integer; begin BinSearchInt:=nil; while left<=right do begin pivot:=(left+right) div 2; if arr[pivot].nameidPtrUInt(query) then right:=pivot-1 else begin BinSearchInt:=@arr[pivot]; exit; end; end; end; function BinSearchRes(root : PResInfoNode; aDesc : PChar) : PResInfoNode; var aID : PtrUint; begin if InternalIsIntResource(aDesc,aID) then BinSearchRes:=BinSearchInt(GetResInfoPtr(root^.subptr),PChar(aID), root^.ncounthandle,root^.ncounthandle+root^.idcountsize-1) else BinSearchRes:=BinSearchStr(GetResInfoPtr(root^.subptr),aDesc,0, root^.ncounthandle-1); end; //Returns a pointer to a name node. function InternalFindResource(ResourceName, ResourceType: PChar): PResInfoNode; begin InternalFindResource:=nil; if ResHeader=nil then exit; InternalFindResource:=GetResInfoPtr(sizeof(TExtHeader)); InternalFindResource:=BinSearchRes(InternalFindResource,ResourceType); if InternalFindResource<>nil then InternalFindResource:=BinSearchRes(InternalFindResource,ResourceName); end; function FindSubLanguage(aPtr : PResInfoNode; aLangID : word; aMask: word) : PResInfoNode; var arr : PResInfoNode; i : longword; begin FindSubLanguage:=nil; arr:=GetResInfoPtr(aPtr^.subptr); i:=0; while i0 then begin // writeln('fpfstat failed'); FpClose(fd); exit; end; // writeln('fpfstat suceeded'); fd_size:=fdstat.st_size; ResHeader:=PExtHeader(Fpmmap(nil,fd_size,PROT_READ or PROT_WRITE, MAP_PRIVATE,fd,0)); // writeln('fpmmap returned ',PtrInt(ResHeader)); if PtrInt(ResHeader)=-1 then begin FpClose(fd); exit; end; if (ResHeader^.magic<>FPCRES_MAGIC) or (ResHeader^.version<>fpcres_version) then begin FpClose(fd); exit; end; // writeln('magic ok'); if ResHeader^.endianess<>FPCRES_ENDIAN then begin ResHeader^.count:=SwapEndian(ResHeader^.count); ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize); ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize); FixResEndian; end; reshandles:=GetMem(sizeof(Pointer)*ResHeader^.count); FillByte(reshandles^,sizeof(Pointer)*ResHeader^.count,0); end; procedure FinalizeResources; begin if ResHeader=nil then exit; FreeMem(reshandles); Fpmunmap(ResHeader,fd_size); FpClose(fd); end; {$ENDIF} {$IFDEF EXTRES_GENERIC} procedure InitResources; var respath : pchar; tmp : longword; tmpptr : pbyte; label ExitErrMem, ExitErrFile, ExitNoErr; begin respath:=GetExtResPath; // writeln('respath ',respath); Assign(fd,respath); FreeMem(respath); {$I-} Reset(fd,1); {$I+} if IOResult<>0 then exit; // writeln('file opened'); ResHeader:=GetMem(sizeof(TExtHeader)); if ResHeader=nil then goto ExitErrFile; {$I-} BlockRead(fd,ResHeader^,sizeof(TExtHeader),tmp); {$I+} if (IOResult<>0) or (tmp<>sizeof(TExtHeader)) then goto ExitErrMem; if (ResHeader^.magic<>FPCRES_MAGIC) or (ResHeader^.version<>fpcres_version) then goto ExitErrMem; // writeln('magic ok'); if ResHeader^.endianess<>FPCRES_ENDIAN then begin ResHeader^.count:=SwapEndian(ResHeader^.count); ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize); ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize); end; SysReallocMem(ResHeader,ResHeader^.hdrsize); if ResHeader=nil then goto ExitErrFile; tmpptr:=pbyte(ResHeader); inc(tmpptr,sizeof(TExtHeader)); {$I-} BlockRead(fd,tmpptr^,ResHeader^.hdrsize-sizeof(TExtHeader),tmp); {$I+} if (IOResult<>0) or (tmp<>ResHeader^.hdrsize-sizeof(TExtHeader)) then goto ExitErrMem; if ResHeader^.endianess<>FPCRES_ENDIAN then FixResEndian; reshandles:=GetMem(sizeof(TResHandle)*ResHeader^.count); FillByte(reshandles^,sizeof(TResHandle)*ResHeader^.count,0); goto ExitNoErr; ExitErrMem: FreeMem(ResHeader); ResHeader:=nil; ExitErrFile: {$I-} Close(fd); {$I+} ExitNoErr: end; procedure FinalizeResources; begin if ResHeader=nil then exit; FreeMem(reshandles); FreeMem(ResHeader); Close(fd); end; {$ENDIF} (***************************************************************************** Public Resource Functions *****************************************************************************) Function ExtHINSTANCE : TFPResourceHMODULE; begin ExtHINSTANCE:=0; end; function ExtEnumResourceTypes(ModuleHandle : TFPResourceHMODULE; EnumFunc : EnumResTypeProc; lParam : PtrInt) : LongBool; var ptr : PResInfoNode; totn, totid, i : longword; pc : pchar; begin ExtEnumResourceTypes:=False; if ResHeader=nil then exit; ptr:=GetResInfoPtr(sizeof(TExtHeader)); totn:=ptr^.ncounthandle; totid:=totn+ptr^.idcountsize; ptr:=GetResInfoPtr(ptr^.subptr); ExtEnumResourceTypes:=true; i:=0; while iusedhandles) then exit; ExtLoadResource:=TFPResourceHGLOBAL(GetPtr(PResInfoNode(reshandles[ResHandle-1])^.subptr)); end; Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool; begin ExtFreeResource:=(ResHeader<>nil); end; Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord; begin ExtSizeofResource:=0; if ResHeader=nil then exit; if (ResHandle<=0) or (ResHandle>usedhandles) then exit; ExtSizeofResource:=PResInfoNode(reshandles[ResHandle-1])^.idcountsize; end; {$ENDIF} {$IFDEF EXTRES_GENERIC} (* Resource data memory layout: -2*sizeof(pointer) Reference count -sizeof(pointer) Pointer to resource info 0 Resource data *) Function ExtLoadResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): TFPResourceHGLOBAL; var ptr : PPtrUInt; tmp : longword; begin ExtLoadResource:=0; if ResHeader=nil then exit; if (ResHandle<=0) or (ResHandle>usedhandles) then exit; if reshandles[ResHandle-1].ptr=nil then begin {$I-} Seek(fd,reshandles[ResHandle-1].info^.subptr); {$I+} if IOResult<>0 then exit; ptr:=GetMem(reshandles[ResHandle-1].info^.idcountsize+2*sizeof(PtrUint)); if ptr=nil then exit; ptr^:=1; //refcount inc(ptr); ptr^:=PtrUInt(reshandles[ResHandle-1].info); //ptr to resource info inc(ptr); {$I-} BlockRead(fd,ptr^,reshandles[ResHandle-1].info^.idcountsize,tmp); {$I+} if (IOResult<>0) or (tmp<>reshandles[ResHandle-1].info^.idcountsize) then begin FreeMem(ptr); exit; end; reshandles[ResHandle-1].ptr:=ptr; end else begin ptr:=reshandles[ResHandle-1].ptr; dec(ptr,2); inc(ptr^,1); //increase reference count end; ExtLoadResource:=TFPResourceHGLOBAL(reshandles[ResHandle-1].ptr); end; Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool; var ptrinfo : PResInfoNode; ptr : PPtrUInt; begin ExtFreeResource:=(ResHeader<>nil); if not ExtFreeResource then exit; ptr:=PPtrUInt(ResData); dec(ptr,2); dec(ptr^); //decrease reference count if ptr^=0 then begin inc(ptr); ptrinfo:=PResInfoNode(ptr^); dec(ptr); FreeMem(ptr); reshandles[ptrinfo^.ncounthandle-1].ptr:=nil; end; ExtFreeResource:=true; end; Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord; var ptrinfo : PResInfoNode; begin ExtSizeofResource:=0; if ResHeader=nil then exit; if (ResHandle<=0) or (ResHandle>usedhandles) then exit; ptrinfo:=PResInfoNode(reshandles[ResHandle-1].info); ExtSizeofResource:=ptrinfo^.idcountsize; end; {$ENDIF} Function ExtLockResource(ResData: TFPResourceHGLOBAL): Pointer; begin ExtLockResource:=Nil; if ResHeader=nil then exit; ExtLockResource:=Pointer(ResData); end; Function ExtUnlockResource(ResData: TFPResourceHGLOBAL): LongBool; begin ExtUnlockResource:=(ResHeader<>nil); end; const ExternalResourceManager : TResourceManager = ( HINSTANCEFunc : @ExtHINSTANCE; EnumResourceTypesFunc : @ExtEnumResourceTypes; EnumResourceNamesFunc : @ExtEnumResourceNames; EnumResourceLanguagesFunc : @ExtEnumResourceLanguages; FindResourceFunc : @ExtFindResource; FindResourceExFunc : @ExtFindResourceEx; LoadResourceFunc : @ExtLoadResource; SizeofResourceFunc : @ExtSizeofResource; LockResourceFunc : @ExtLockResource; UnlockResourceFunc : @ExtUnlockResource; FreeResourceFunc : @ExtFreeResource; );