1{
2    This file is part of the Free Pascal run time library.
3    Copyright (c) 2008 by Giulio Bernardi
4
5    Resource support as external files
6
7    See the file COPYING.FPC, included in this distribution,
8    for details about the copyright.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
14 **********************************************************************}
15{
16  This file implements two kinds of external resource support:
17   - one for systems that support the mmap call (usually unix-like oses)
18   - one fallback implementation based on pascal files and GetMem/FreeMem
19
20  Be sure to define EXTRES_MMAP or EXTRES_GENERIC before including this file!
21}
22{$IF defined(EXTRES_MMAP) and defined(EXTRES_GENERIC)}
23{$FATAL EXTRES_MMAP and EXTRES_GENERIC can't be defined together}
24{$ENDIF}
25
26{$IF (not defined(EXTRES_MMAP)) and (not defined(EXTRES_GENERIC))}
27{$FATAL EXTRES_MMAP or EXTRES_GENERIC must be defined}
28{$ENDIF}
29
30const
31  FPCRES_MAGIC = 'FPCRES';
32  FPCRES_VERSION = 1;
33  {$IFDEF ENDIAN_BIG}
34  FPCRES_ENDIAN = 1;
35  {$ENDIF}
36  {$IFDEF ENDIAN_LITTLE}
37  FPCRES_ENDIAN = 2;
38  {$ENDIF}
39  FPCRES_EXT = '.fpcres';
40
41type
42  TExtHeader = packed record
43    magic : array[0..5] of char;//'FPCRES'
44    version : byte;             //EXT_CURRENT_VERSION
45    endianess : byte;           //EXT_ENDIAN_BIG or EXT_ENDIAN_LITTLE
46    count : longword;           //resource count
47    nodesize : longword;        //size of header (up to string table, excluded)
48    hdrsize  : longword;        //size of header (up to string table, included)
49    reserved1 : longword;
50    reserved2 : longword;
51    reserved3 : longword;
52  end;
53  PExtHeader = ^TExtHeader;
54
55  TResInfoNode = packed record
56    nameid : longword;          //name offset / integer ID / languageID
57    ncounthandle : longword;    //named sub-entries count/resource handle
58    idcountsize : longword;     //id sub-entries count / resource size
59    subptr : longword;          //first sub-entry offset
60  end;
61  PResInfoNode = ^TResInfoNode;
62
63  {$IFDEF EXTRES_GENERIC}
64  TResHandle = record
65    info : PResInfoNode;
66    ptr : Pointer;
67  end;
68  PResHandle = ^TResHandle;
69  {$ENDIF}
70
71var ResHeader : PExtHeader = nil;
72    usedhandles : longword = 0;
73    {$IFDEF EXTRES_MMAP}
74    fd : integer;
75    fd_size : longword;
76    reshandles : PPointer = nil;
77    {$ENDIF}
78    {$IFDEF EXTRES_GENERIC}
79    fd : file;
80    reshandles : PResHandle = nil;
81    {$ENDIF}
82
83(*****************************************************************************
84                             Private Helper Functions
85*****************************************************************************)
86
87//resource functions are case insensitive... copied from genstr.inc
88function ResStrIComp(Str1, Str2 : PChar): SizeInt;
89var
90  counter: SizeInt;
91  c1, c2: char;
92begin
93  counter := 0;
94  c1 := upcase(str1[counter]);
95  c2 := upcase(str2[counter]);
96  while c1 = c2 do
97  begin
98    if (c1 = #0) or (c2 = #0) then break;
99    inc(counter);
100    c1 := upcase(str1[counter]);
101    c2 := upcase(str2[counter]);
102  end;
103  ResStrIComp := ord(c1) - ord(c2);
104end;
105
106{!fixme!}
107//function InternalIsIntResource(aStr : pchar; out aInt : PtrUint) : boolean;
108function InternalIsIntResource(aStr : pchar; var aInt : PtrUint) : boolean;
109var i : integer;
110    s : shortstring;
111    code : word;
112begin
113  InternalIsIntResource:=((PtrUInt(aStr) shr 16)=0);
114  if InternalIsIntResource then aInt:=PtrUInt(aStr)
115  else
116  begin
117    //a string like #number specifies an integer id
118    if aStr[0]='#' then
119    begin
120      i:=1;
121      while aStr[i]<>#0 do
122        inc(i);
123      if i>256 then i:=256;
124      s[0]:=chr(i-1);
125      Move(aStr[1],s[1],i-1);
126      Val(s,aInt,code);
127      InternalIsIntResource:=code=0;
128    end;
129  end;
130end;
131
132function GetResInfoPtr(const offset : longword) : PResInfoNode; inline;
133begin
134  GetResInfoPtr:=PResInfoNode(PtrUInt(ResHeader)+offset);
135end;
136
137function GetPchar(const offset : longword) : Pchar; inline;
138begin
139  GetPchar:=Pchar(PtrUInt(ResHeader)+offset);
140end;
141
142function GetPtr(const offset : longword) : Pointer; inline;
143begin
144  GetPtr:=Pointer(PtrUInt(ResHeader)+offset);
145end;
146
147procedure FixResEndian;
148var ptr : plongword;
149    blockend : plongword;
150begin
151  //all info nodes reside in a contiguos block of memory.
152  //they are all 16 bytes long and made by longwords
153  //so, simply swap each longword in the block
154  ptr:=GetPtr(sizeof(TExtHeader));
155  blockend:=GetPtr(ResHeader^.nodesize);
156  while ptr<blockend do
157  begin
158    ptr^:=SwapEndian(ptr^);
159    inc(ptr);
160  end;
161end;
162
163function GetExtResPath : pchar;
164var len, i : integer;
165    pathstr : shortstring;
166begin
167  pathstr:=paramstr(0);
168  len:=byte(pathstr[0]);
169  i:=len;
170  //writeln('exe name is ',pathstr);
171  //find position of extension
172  while (i>0) and (not (pathstr[i] in ['.',DirectorySeparator])) do
173    dec(i);
174  if (i>0) and (pathstr[i]='.') then dec(i)
175  else i:=len;
176  pathstr[0]:=Chr(i);
177  pathstr:=pathstr+FPCRES_EXT;
178  len:=byte(pathstr[0]);
179  GetExtResPath:=GetMem(len+1);
180  Move(pathstr[1],GetExtResPath[0],len);
181  GetExtResPath[len]:=#0;
182  //writeln('Resource file is ',GetExtResPath);
183end;
184
185function BinSearchStr(arr : PResInfoNode; query : pchar; left, right : integer)
186: PResInfoNode;
187var pivot, res : integer;
188    resstr : pchar;
189begin
190  BinSearchStr:=nil;
191  while left<=right do
192  begin
193    pivot:=(left+right) div 2;
194    resstr:=GetPchar(arr[pivot].nameid);
195    res:=ResStrIComp(resstr,query);
196    if res<0 then left:=pivot+1
197    else if res>0 then right:=pivot-1
198    else
199    begin
200      BinSearchStr:=@arr[pivot];
201      exit;
202    end;
203  end;
204end;
205
206function BinSearchInt(arr : PResInfoNode; query : pchar; left, right : integer)
207: PResInfoNode;
208var pivot : integer;
209begin
210  BinSearchInt:=nil;
211  while left<=right do
212  begin
213    pivot:=(left+right) div 2;
214    if arr[pivot].nameid<PtrUInt(query) then left:=pivot+1
215    else if arr[pivot].nameid>PtrUInt(query) then right:=pivot-1
216    else
217    begin
218      BinSearchInt:=@arr[pivot];
219      exit;
220    end;
221  end;
222end;
223
224function BinSearchRes(root : PResInfoNode; aDesc : PChar) : PResInfoNode;
225var aID : PtrUint;
226begin
227  if InternalIsIntResource(aDesc,aID) then
228    BinSearchRes:=BinSearchInt(GetResInfoPtr(root^.subptr),PChar(aID),
229      root^.ncounthandle,root^.ncounthandle+root^.idcountsize-1)
230  else
231    BinSearchRes:=BinSearchStr(GetResInfoPtr(root^.subptr),aDesc,0,
232      root^.ncounthandle-1);
233end;
234
235//Returns a pointer to a name node.
236function InternalFindResource(ResourceName, ResourceType: PChar):
237 PResInfoNode;
238begin
239  InternalFindResource:=nil;
240  if ResHeader=nil then exit;
241  InternalFindResource:=GetResInfoPtr(sizeof(TExtHeader));
242
243  InternalFindResource:=BinSearchRes(InternalFindResource,ResourceType);
244  if InternalFindResource<>nil then
245    InternalFindResource:=BinSearchRes(InternalFindResource,ResourceName);
246end;
247
248function FindSubLanguage(aPtr : PResInfoNode; aLangID : word; aMask: word) : PResInfoNode;
249var arr : PResInfoNode;
250    i : longword;
251begin
252  FindSubLanguage:=nil;
253  arr:=GetResInfoPtr(aPtr^.subptr);
254  i:=0;
255  while i<aPtr^.idcountsize do
256  begin
257    if (PtrUInt(arr[i].nameid) and aMask)=(aLangID and aMask) then
258    begin
259      FindSubLanguage:=@arr[i];
260      exit;
261    end;
262    inc(i);
263  end;
264end;
265
266{$IFDEF EXTRES_MMAP}
267procedure InitResources;
268const
269  PROT_READ  = 1;
270  PROT_WRITE = 2;
271var respath : pchar;
272    fdstat : stat;
273begin
274  respath:=GetExtResPath;
275//  writeln('respath ',respath);
276  fd:=FpOpen(respath,O_RDONLY,0);
277//  writeln('fpopen returned ',fd);
278  FreeMem(respath);
279  if fd=-1 then exit;
280  if FpFStat(fd,fdstat)<>0 then
281  begin
282//    writeln('fpfstat failed');
283    FpClose(fd);
284    exit;
285  end;
286//  writeln('fpfstat suceeded');
287  fd_size:=fdstat.st_size;
288  ResHeader:=PExtHeader(Fpmmap(nil,fd_size,PROT_READ or PROT_WRITE,
289    MAP_PRIVATE,fd,0));
290//  writeln('fpmmap returned ',PtrInt(ResHeader));
291  if PtrInt(ResHeader)=-1 then
292  begin
293    FpClose(fd);
294    exit;
295  end;
296  if (ResHeader^.magic<>FPCRES_MAGIC) or
297    (ResHeader^.version<>fpcres_version) then
298  begin
299    FpClose(fd);
300    exit;
301  end;
302//  writeln('magic ok');
303  if ResHeader^.endianess<>FPCRES_ENDIAN then
304  begin
305    ResHeader^.count:=SwapEndian(ResHeader^.count);
306    ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize);
307    ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize);
308    FixResEndian;
309  end;
310  reshandles:=GetMem(sizeof(Pointer)*ResHeader^.count);
311  FillByte(reshandles^,sizeof(Pointer)*ResHeader^.count,0);
312end;
313
314procedure FinalizeResources;
315begin
316  if ResHeader=nil then exit;
317  FreeMem(reshandles);
318  Fpmunmap(ResHeader,fd_size);
319  FpClose(fd);
320end;
321{$ENDIF}
322
323{$IFDEF EXTRES_GENERIC}
324procedure InitResources;
325var respath : pchar;
326    tmp : longword;
327    tmpptr : pbyte;
328label ExitErrMem, ExitErrFile, ExitNoErr;
329begin
330  respath:=GetExtResPath;
331//  writeln('respath ',respath);
332  Assign(fd,respath);
333  FreeMem(respath);
334  {$I-}
335  Reset(fd,1);
336  {$I+}
337  if IOResult<>0 then exit;
338//  writeln('file opened');
339  ResHeader:=GetMem(sizeof(TExtHeader));
340  if ResHeader=nil then goto ExitErrFile;
341  {$I-}
342  BlockRead(fd,ResHeader^,sizeof(TExtHeader),tmp);
343  {$I+}
344  if (IOResult<>0) or (tmp<>sizeof(TExtHeader)) then goto ExitErrMem;
345  if (ResHeader^.magic<>FPCRES_MAGIC) or (ResHeader^.version<>fpcres_version)
346    then goto ExitErrMem;
347//  writeln('magic ok');
348  if ResHeader^.endianess<>FPCRES_ENDIAN then
349  begin
350    ResHeader^.count:=SwapEndian(ResHeader^.count);
351    ResHeader^.nodesize:=SwapEndian(ResHeader^.nodesize);
352    ResHeader^.hdrsize:=SwapEndian(ResHeader^.hdrsize);
353  end;
354  SysReallocMem(ResHeader,ResHeader^.hdrsize);
355  if ResHeader=nil then goto ExitErrFile;
356  tmpptr:=pbyte(ResHeader);
357  inc(tmpptr,sizeof(TExtHeader));
358  {$I-}
359  BlockRead(fd,tmpptr^,ResHeader^.hdrsize-sizeof(TExtHeader),tmp);
360  {$I+}
361  if (IOResult<>0) or (tmp<>ResHeader^.hdrsize-sizeof(TExtHeader)) then goto ExitErrMem;
362  if ResHeader^.endianess<>FPCRES_ENDIAN then
363    FixResEndian;
364  reshandles:=GetMem(sizeof(TResHandle)*ResHeader^.count);
365  FillByte(reshandles^,sizeof(TResHandle)*ResHeader^.count,0);
366  goto ExitNoErr;
367
368  ExitErrMem:
369    FreeMem(ResHeader);
370    ResHeader:=nil;
371  ExitErrFile:
372    {$I-}
373    Close(fd);
374    {$I+}
375  ExitNoErr:
376end;
377
378procedure FinalizeResources;
379begin
380  if ResHeader=nil then exit;
381  FreeMem(reshandles);
382  FreeMem(ResHeader);
383  Close(fd);
384end;
385{$ENDIF}
386
387(*****************************************************************************
388                             Public Resource Functions
389*****************************************************************************)
390
391Function ExtHINSTANCE : TFPResourceHMODULE;
392begin
393  ExtHINSTANCE:=0;
394end;
395
396function ExtEnumResourceTypes(ModuleHandle : TFPResourceHMODULE; EnumFunc : EnumResTypeProc; lParam : PtrInt) : LongBool;
397var ptr : PResInfoNode;
398    totn, totid, i : longword;
399    pc : pchar;
400begin
401  ExtEnumResourceTypes:=False;
402  if ResHeader=nil then exit;
403  ptr:=GetResInfoPtr(sizeof(TExtHeader));
404  totn:=ptr^.ncounthandle;
405  totid:=totn+ptr^.idcountsize;
406  ptr:=GetResInfoPtr(ptr^.subptr);
407  ExtEnumResourceTypes:=true;
408  i:=0;
409  while i<totn do //named entries
410  begin
411    pc:=GetPChar(ptr[i].nameid);
412    if not EnumFunc(ModuleHandle,pc,lParam) then exit;
413    inc(i);
414  end;
415  while i<totid do
416  begin
417    if not EnumFunc(ModuleHandle,PChar(ptr[i].nameid),lParam) then exit;
418    inc(i);
419  end;
420end;
421
422function ExtEnumResourceNames(ModuleHandle : TFPResourceHMODULE; ResourceType : PChar; EnumFunc : EnumResNameProc; lParam : PtrInt) : LongBool;
423var ptr : PResInfoNode;
424    totn, totid, i : longword;
425    pc : pchar;
426begin
427  ExtEnumResourceNames:=False;
428  if ResHeader=nil then exit;
429  ptr:=GetResInfoPtr(sizeof(TExtHeader));
430
431  ptr:=BinSearchRes(ptr,ResourceType);
432  if ptr=nil then exit;
433
434  totn:=ptr^.ncounthandle;
435  totid:=totn+ptr^.idcountsize;
436  ptr:=GetResInfoPtr(ptr^.subptr);
437  ExtEnumResourceNames:=true;
438  i:=0;
439  while i<totn do //named entries
440  begin
441    pc:=GetPChar(ptr[i].nameid);
442    if not EnumFunc(ModuleHandle,ResourceType,pc,lParam) then exit;
443    inc(i);
444  end;
445  while i<totid do
446  begin
447    if not EnumFunc(ModuleHandle,ResourceType,PChar(ptr[i].nameid),lParam) then exit;
448    inc(i);
449  end;
450end;
451
452function ExtEnumResourceLanguages(ModuleHandle : TFPResourceHMODULE; ResourceType, ResourceName : PChar; EnumFunc : EnumResLangProc; lParam : PtrInt) : LongBool;
453var ptr : PResInfoNode;
454    tot, i : integer;
455begin
456  ExtEnumResourceLanguages:=False;
457  ptr:=InternalFindResource(ResourceName,ResourceType);
458  if ptr=nil then exit;
459
460  tot:=ptr^.idcountsize;
461  ptr:=GetResInfoPtr(ptr^.subptr);
462  ExtEnumResourceLanguages:=true;
463  i:=0;
464  while i<tot do
465  begin
466    if not EnumFunc(ModuleHandle,ResourceType,ResourceName,PtrUInt(ptr[i].nameid),lParam) then exit;
467    inc(i);
468  end;
469end;
470
471Function ExtFindResource(ModuleHandle: TFPResourceHMODULE; ResourceName, ResourceType: PChar)
472: TFPResourceHandle;
473var ptr : PResInfoNode;
474begin
475  ExtFindResource:=0;
476  ptr:=InternalFindResource(ResourceName,ResourceType);
477  if ptr=nil then exit;
478
479  //first language id
480  ptr:=GetResInfoPtr(ptr^.subptr);
481  if ptr^.ncounthandle=0 then
482  begin
483    {$IFDEF EXTRES_MMAP}
484    reshandles[usedhandles]:=ptr;
485    {$ENDIF}
486    {$IFDEF EXTRES_GENERIC}
487    reshandles[usedhandles].info:=ptr;
488    {$ENDIF}
489    inc(usedhandles);
490    ptr^.ncounthandle:=usedhandles;
491  end;
492  ExtFindResource:=ptr^.ncounthandle;
493end;
494
495Function ExtFindResourceEx(ModuleHandle: TFPResourceHMODULE; ResourceType,
496  ResourceName: PChar; Language : word): TFPResourceHandle;
497const LANG_NEUTRAL = 0;
498      LANG_ENGLISH = 9;
499var nameptr,ptr : PResInfoNode;
500begin
501  ExtFindResourceEx:=0;
502  nameptr:=InternalFindResource(ResourceName,ResourceType);
503  if nameptr=nil then exit;
504
505  //try exact match
506  ptr:=FindSubLanguage(nameptr,Language,$FFFF);
507  //try primary language
508  if ptr=nil then
509    ptr:=FindSubLanguage(nameptr,Language,$3FF);
510  //try language neutral
511  if ptr=nil then
512    ptr:=FindSubLanguage(nameptr,LANG_NEUTRAL,$3FF);
513  //try english
514  if ptr=nil then
515    ptr:=FindSubLanguage(nameptr,LANG_ENGLISH,$3FF);
516  //nothing found, return the first one
517  if ptr=nil then
518    ptr:=GetResInfoPtr(nameptr^.subptr);
519
520  if ptr^.ncounthandle=0 then
521  begin
522    {$IFDEF EXTRES_MMAP}
523    reshandles[usedhandles]:=ptr;
524    {$ENDIF}
525    {$IFDEF EXTRES_GENERIC}
526    reshandles[usedhandles].info:=ptr;
527    {$ENDIF}
528    inc(usedhandles);
529    ptr^.ncounthandle:=usedhandles;
530  end;
531  ExtFindResourceEx:=ptr^.ncounthandle;
532end;
533
534{$IFDEF EXTRES_MMAP}
535Function ExtLoadResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): TFPResourceHGLOBAL;
536begin
537  ExtLoadResource:=0;
538  if ResHeader=nil then exit;
539  if (ResHandle<=0) or (ResHandle>usedhandles) then exit;
540  ExtLoadResource:=TFPResourceHGLOBAL(GetPtr(PResInfoNode(reshandles[ResHandle-1])^.subptr));
541end;
542
543Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool;
544begin
545  ExtFreeResource:=(ResHeader<>nil);
546end;
547
548Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord;
549begin
550  ExtSizeofResource:=0;
551  if ResHeader=nil then exit;
552  if (ResHandle<=0) or (ResHandle>usedhandles) then exit;
553  ExtSizeofResource:=PResInfoNode(reshandles[ResHandle-1])^.idcountsize;
554end;
555{$ENDIF}
556
557{$IFDEF EXTRES_GENERIC}
558(*
559Resource data memory layout:
560-2*sizeof(pointer)  Reference count
561  -sizeof(pointer)  Pointer to resource info
562                 0  Resource data
563*)
564Function ExtLoadResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): TFPResourceHGLOBAL;
565var ptr : PPtrUInt;
566    tmp : longword;
567begin
568  ExtLoadResource:=0;
569  if ResHeader=nil then exit;
570  if (ResHandle<=0) or (ResHandle>usedhandles) then exit;
571
572  if reshandles[ResHandle-1].ptr=nil then
573  begin
574    {$I-}
575    Seek(fd,reshandles[ResHandle-1].info^.subptr);
576    {$I+}
577    if IOResult<>0 then exit;
578    ptr:=GetMem(reshandles[ResHandle-1].info^.idcountsize+2*sizeof(PtrUint));
579    if ptr=nil then exit;
580    ptr^:=1; //refcount
581    inc(ptr);
582    ptr^:=PtrUInt(reshandles[ResHandle-1].info); //ptr to resource info
583    inc(ptr);
584    {$I-}
585    BlockRead(fd,ptr^,reshandles[ResHandle-1].info^.idcountsize,tmp);
586    {$I+}
587    if (IOResult<>0) or (tmp<>reshandles[ResHandle-1].info^.idcountsize) then
588    begin
589      FreeMem(ptr);
590      exit;
591    end;
592    reshandles[ResHandle-1].ptr:=ptr;
593  end
594  else
595  begin
596    ptr:=reshandles[ResHandle-1].ptr;
597    dec(ptr,2);
598    inc(ptr^,1); //increase reference count
599  end;
600  ExtLoadResource:=TFPResourceHGLOBAL(reshandles[ResHandle-1].ptr);
601end;
602
603Function ExtFreeResource(ResData: TFPResourceHGLOBAL): LongBool;
604var ptrinfo : PResInfoNode;
605    ptr : PPtrUInt;
606begin
607  ExtFreeResource:=(ResHeader<>nil);
608  if not ExtFreeResource then exit;
609  ptr:=PPtrUInt(ResData);
610  dec(ptr,2);
611  dec(ptr^); //decrease reference count
612  if ptr^=0 then
613  begin
614    inc(ptr);
615    ptrinfo:=PResInfoNode(ptr^);
616    dec(ptr);
617    FreeMem(ptr);
618    reshandles[ptrinfo^.ncounthandle-1].ptr:=nil;
619  end;
620  ExtFreeResource:=true;
621end;
622
623Function ExtSizeofResource(ModuleHandle: TFPResourceHMODULE; ResHandle: TFPResourceHandle): LongWord;
624var ptrinfo : PResInfoNode;
625begin
626  ExtSizeofResource:=0;
627  if ResHeader=nil then exit;
628  if (ResHandle<=0) or (ResHandle>usedhandles) then exit;
629  ptrinfo:=PResInfoNode(reshandles[ResHandle-1].info);
630  ExtSizeofResource:=ptrinfo^.idcountsize;
631end;
632{$ENDIF}
633
634Function ExtLockResource(ResData: TFPResourceHGLOBAL): Pointer;
635begin
636  ExtLockResource:=Nil;
637  if ResHeader=nil then exit;
638  ExtLockResource:=Pointer(ResData);
639end;
640
641Function ExtUnlockResource(ResData: TFPResourceHGLOBAL): LongBool;
642begin
643  ExtUnlockResource:=(ResHeader<>nil);
644end;
645
646const
647  ExternalResourceManager : TResourceManager =
648  (
649    HINSTANCEFunc : @ExtHINSTANCE;
650    EnumResourceTypesFunc : @ExtEnumResourceTypes;
651    EnumResourceNamesFunc : @ExtEnumResourceNames;
652    EnumResourceLanguagesFunc : @ExtEnumResourceLanguages;
653    FindResourceFunc : @ExtFindResource;
654    FindResourceExFunc : @ExtFindResourceEx;
655    LoadResourceFunc : @ExtLoadResource;
656    SizeofResourceFunc : @ExtSizeofResource;
657    LockResourceFunc : @ExtLockResource;
658    UnlockResourceFunc : @ExtUnlockResource;
659    FreeResourceFunc : @ExtFreeResource;
660  );
661