Module: Win32-duim Synopsis: Windows resource decoding Author: Roman Budzianowski, Scott McKay Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define inline function pointer+ (p :: , offset :: ) => (p :: ) pointer-value-address(p, index: offset) end function pointer+; define inline function pointer-address-16 (p) => (p-16) %logand(\%+(p, 1), as(, #xFFFFFFFE)) end function pointer-address-16; define inline function pointer-address-32 (p) => (p-32) %logand(\%+(p, 3), as(, #xFFFFFFFC)) end function pointer-address-32; /// Basic types and classes define constant = ; define constant = type-union(, , ); define constant = type-union(, , ); // define constant $button-class :: = #x0080; // define constant $edit-class :: = #x0081; // define constant $static-class :: = #x0082; // define constant $list-box-class :: = #x0083; // define constant $scroll-bar-class :: = #x0084; // define constant $combo-box-class :: = #x0085; /// Basic protocols define generic resource-id (resource :: ) => (id :: ); define generic resource-type (resource :: ) => (type :: ); define abstract class () end class ; define generic window-position (resource :: ) => (x :: , y :: ); define generic window-size (resource :: ) => (w :: , h :: ); define abstract class () end class ; define generic gadget-count (resource :: ) => (n :: ); define abstract class () end class ; define generic encode-resource (resource :: type-union(, )) => (id :: ); define generic decode-resource (raw-id :: ) => (id :: type-union(, )); define generic lookup-resource (type :: , id :: ) => (resource :: ); define generic lookup-control (window :: , id :: ) => (resource :: ); /// Decoding define sealed method decode-resource (raw-id :: ) => (resource-id :: type-union(, )) let value = pointer-address(raw-id); if (zero?(%logand(as(, #xFFFF0000), value))) as(, value) else as(, raw-id) end end method decode-resource; /// Encoding define sealed method encode-resource (resource-id :: ) => (raw-id :: ) MAKEINTRESOURCE(resource-id) end method encode-resource; define sealed method encode-resource (resource-id :: ) => (raw-id :: ) as(, resource-id) // this should work end method encode-resource; define sealed method encode-resource (resource-id :: ) => (raw-id :: ) as(, resource-id) // this should work end method encode-resource; define sealed method encode-resource (resource-id :: ) => (raw-id :: ) as(, as(, resource-id)) end method encode-resource; /// Resource classes define sealed class () slot %resource :: , required-init-keyword: resource:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define abstract class () slot resource-handle :: , init-keyword: resource-handle:; slot resource-id :: , init-keyword: resource-id:; slot resource-size :: , init-keyword: resource-size:; end class ; // Stand-in for a loaded resource so we can load it lazily... define sealed class () constant slot resource-type-value :: , required-init-keyword: resource-type:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method resource-type (resource :: ) => (resource-type :: ) resource-type-value(resource) end method resource-type; // A resource once it as been loaded into memory define abstract class () constant slot memory-handle :: , init-keyword: memory-handle:; end class ; // Not actually used.... ignore(memory-handle); define method initialize (resource :: , #key resource-description) next-method(); resource-handle(resource) := resource-handle(resource-description); resource-id(resource) := resource-id(resource-description); resource-size(resource) := resource-size(resource-description); end method initialize; define abstract class () end class ; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Dialog resources define sealed class (, ) constant slot dialog-template :: , required-init-keyword: template:; slot dialog-font-name :: = encode-resource(0); slot dialog-font-size :: = 0; constant slot dialog-children = make(); end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method window-position (dialog :: ) => (x :: , y :: ) values(dialog-template(dialog).x-value, dialog-template(dialog).y-value) end method window-position; define sealed method window-size (dialog :: ) => (w :: , h :: ) values(dialog-template(dialog).cx-value, dialog-template(dialog).cy-value) end method window-size; define sealed method gadget-count (dialog :: ) => (n :: ) dialog-template(dialog).cdit-value end method gadget-count; define generic register-child (dialog :: , child :: , id :: ) => (); define sealed method register-child (dialog :: , child :: , id :: ) => () dialog-children(dialog)[encode-resource(id)] := child; end method register-child; define sealed method register-child (dialog :: , child :: , id :: ) => () dialog-children(dialog)[encode-resource(id)] := child end method register-child; /// Control resources define sealed class (, ) constant slot control-template :: , required-init-keyword: template:; end class ; define method initialize (resource :: , #key) next-method(); resource-id(resource) := encode-resource(control-template(resource).id-value) end method initialize; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method window-position (control :: ) => (x :: , y :: ) values(control-template(control).x-value, control-template(control).y-value) end method window-position; define sealed method window-size (control :: ) => (w :: , h :: ) values(control-template(control).cx-value, control-template(control).cy-value) end method window-size; define sealed method get-resource-id (control :: ) => (id :: ) control-template(control).id-value end method get-resource-id; /// Toolbar resources define sealed class (, ) end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Resource tables define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method resource-equal (id1 :: , id2 :: ) => (equal? :: ) pointer-address(id1) = pointer-address(id2) end method resource-equal; define sealed method resource-hash (id :: , hash-state :: ) => (hash-value, hash-state) let (value, state) = object-hash(decode-resource(id), hash-state); values(value, state) end method resource-hash; define sealed method table-protocol (table :: ) => (test-function :: , hash-function :: ) values(resource-equal, resource-hash) end method table-protocol; /// Resource databases define abstract class () end class ; define generic processing-type (database :: , type :: ); define generic store-resource-name (database :: , name :: ) => (); define generic store-resource-details (database :: , handle :: , resource-size :: , language-id :: ) => (); define sealed class () constant slot %resources :: = make(); slot %module :: ; // the current instance handle end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Initializing resource databases // The single resource database for the current application define variable *resource-database* :: = make(); // These are used for booting the application define thread variable *current-database* = #f; define thread variable *current-type* = #f; define thread variable *current-type-table* = #f; define thread variable *current-resource* = #f; define function load-default-resources () => (status :: ) let hInstance = application-instance-handle(); *resource-database*.%module := hInstance; enumerate-resources(hInstance, database: *resource-database*) end function load-default-resources; // Must be called with the four thread variables above bound... define sealed method processing-type (database :: , type :: ) let raw-type = encode-resource(type); let type-db = element(database.%resources, raw-type, default: #"not-found"); when (type-db == #"not-found") type-db := make(); database.%resources[raw-type] := type-db end; *current-type* := raw-type; *current-type-table* := type-db; end method processing-type; // Must be called with the four thread variables above bound... define sealed method store-resource-name (database :: , name :: ) => () store-new-resource(database, encode-resource(name)) end method store-resource-name; define sealed method store-resource-name (database :: , name :: ) => () store-new-resource(database, encode-resource(name)) end method store-resource-name; define sealed method store-resource-name (database :: , name :: ) => () store-new-resource(database, name) end method store-resource-name; define sealed method store-new-resource (database :: , name :: ) => () let resource = make(, resource-id: name, resource-type: *current-type*); let wrapper = make(, resource: resource); *current-type-table*[name] := wrapper; *current-resource* := resource; end method store-new-resource; // Must be called with the four thread variables above bound... define sealed method store-resource-details (database :: , handle :: , resource-size :: , language-id :: ) => () resource-handle(*current-resource*) := handle; resource-size(*current-resource*) := resource-size; end method store-resource-details; define sealed method enumerate-resources (handle :: , #key database :: false-or() = #f) => (success? :: ) assert(~null-handle?(handle), "Invalid handle to resource module"); assert(database, "No database supplied"); dynamic-bind (*current-database* = database, *current-type* = #f, *current-type-table* = #f, *current-resource* = #f) let success? = EnumResourceTypes(handle, EnumResTypeProc, 0); success? end end method enumerate-resources; define sealed method enumerate-resource-types (hModule :: , // module handle lpType :: , // address of resource type lParam :: ) // extra parameter, could be used for error checking => (value :: ) unless (null-pointer?(lpType)) processing-type(*current-database*, lpType); // Find the names of all resources of type lpType EnumResourceNames(hModule, lpType, EnumResNameProc, 0) end; #t end method enumerate-resource-types; define callback EnumResTypeProc :: = enumerate-resource-types; define sealed method enumerate-resource-names (hModule :: , // module handle lpType :: , // address of resource type lpName :: , // address of resource name lParam :: ) // extra parameter, could be used for error checking => (value :: ) unless (null-pointer?(lpName)) store-resource-name(*current-database*, lpName); // Find the languages of all resources of type lpType and name lpName EnumResourceLanguages(hModule, lpType, lpName, EnumResLangProc, 0) end; #t end method enumerate-resource-names; define callback EnumResNameProc :: = enumerate-resource-names; define sealed method enumerate-resource-languages (hModule :: , // module handle lpType :: , // address of resource type lpName :: , // address of resource name wLang :: , // resource language lParam :: ) // extra parameter, could be used for error checking => (value :: ) let hResInfo :: = FindResourceEx(hModule, lpType, lpName, wLang); let resource-size = SizeofResource(hModule, hResInfo); store-resource-details(*current-database*, hResInfo, resource-size, wLang); #t end method enumerate-resource-languages; define callback EnumResLangProc :: = enumerate-resource-languages; /// Resource lookup define constant *grok-resource-table* :: = make(); define sealed method lookup-resource (type :: , id :: ) => (resource :: ) let table = *resource-database*.%resources[type]; // doesn't have to be encoded let wrapper = table[encode-resource(id)]; let resource = retrieve-resource(wrapper.%resource, *resource-database*); wrapper.%resource := resource; resource end method lookup-resource; define sealed method lookup-control (dialog :: , id :: ) => (resource :: ) let control = element(dialog-children(dialog), encode-resource(id), default: #f); control | error("No such control id %=", id) end method lookup-control; define sealed method retrieve-resource (resource :: , database :: ) => (resource :: ) resource end method retrieve-resource; define sealed method retrieve-resource (resource :: , database :: ) => (resource :: ) let grokker :: false-or() = element(*grok-resource-table*, resource-type(resource), default: #f); if (grokker) grokker(resource, database.%module) else error("Resource of type %= not supported yet", resource-type(resource)) end end method retrieve-resource; /// Grokking of simple resources //--- $RT-ACCELERATOR not yet handled //--- $RT-FONT not yet handled //--- $RT-FONTDIR not yet handled //--- $RT-MENU not yet handled //--- $RT-RCDATA not yet handled //--- $RT-STRING not yet handled //--- $RT-MESSAGETABLE not yet handled //--- $RT-GROUP-CURSOR not yet handled //--- $RT-GROUP-ICON not yet handled //--- $RT-VERSION not yet handled define function grok-bitmap (resource :: , handle :: ) => (bitmap :: ) // We already have the handle, but need to load the resource let bitmap = LoadBitmap(handle, resource-id(resource)); make(, resource-description: resource, memory-handle: bitmap) end function grok-bitmap; *grok-resource-table*[$RT-BITMAP] := grok-bitmap; define function grok-icon (resource :: , handle :: ) => (icon :: ) // We already have the handle, but need to load the resource let icon = LoadIcon(handle, resource-id(resource)); make(, resource-description: resource, memory-handle: icon) end function grok-icon; *grok-resource-table*[$RT-ICON] := grok-icon; define function grok-cursor (resource :: , handle :: ) => (cursor :: ) // We already have the handle, but need to load the resource let cursor = LoadCursor(handle, resource-id(resource)); make(, resource-description: resource, memory-handle: cursor) end function grok-cursor; *grok-resource-table*[$RT-CURSOR] := grok-cursor; define constant $dlg-template-size :: = 18; define constant $item-template-size :: = 18; define function grok-dialog (resource :: , handle :: ) => (dialog :: ) let resource-address = pointer-address(LoadResource(handle, resource-handle(resource))); let template :: = make(, address: resource-address); let dialog = make(, resource-description: resource, template: template); let template-size :: = 0; unless (null-pointer?(template)) let offset :: = $dlg-template-size; let menu-test-pointer :: = make(, address: \%+(resource-address, offset)); let (menu-resource-id, menu-resource-id-size) = grok-resource-id(menu-test-pointer); offset := offset + menu-resource-id-size; let class-test-pointer :: = make(, address: \%+(resource-address, offset)); let (class-resource-id, class-resource-id-size) = grok-resource-id(class-test-pointer); offset := offset + class-resource-id-size; let title-string-pointer :: = make(, address: \%+(resource-address, offset)); let (title-string, title-size) = grok-resource-string(title-string-pointer); offset := offset + title-size; let font-resource-size :: = 0; when (logand(template.style-value, $DS-SETFONT) ~= 0) let font-size-pointer :: = make(, address: \%+(resource-address, offset)); offset := offset + size-of(); dialog-font-size(dialog) := pointer-value(font-size-pointer); let font-name-pointer :: = pointer+(font-size-pointer, 1); let (font-name, font-name-size) = grok-resource-string(font-name-pointer); dialog-font-name(dialog) := encode-resource(font-name); offset := offset + font-name-size; end; let addr = \%+(resource-address, offset); for (i :: from 1 below template.cdit-value, item = make(, address: pointer-address-32(addr)) then make(, address: pointer-address-32(addr))) let (item-size, control-resource) = grok-item-template(item); register-child(dialog, control-resource, get-resource-id(control-resource)); addr := \%+(pointer-address(item), item-size); finally let (item-size, control-resource) = grok-item-template(item); register-child(dialog, control-resource, get-resource-id(control-resource)); addr := \%+(pointer-address(item), item-size); template-size := as(, \%-(addr, resource-address)); end end; assert(resource-size(dialog) = template-size, "Incorrect dialog resource size retrieved"); dialog end function grok-dialog; *grok-resource-table*[$RT-DIALOG] := grok-dialog; define method grok-item-template (template :: ) => (resource-size :: , resource :: ) let control = make(, template: template); let class-test-pointer :: = make(, address: \%+(pointer-address(template), $item-template-size)); let (class-resource-id, class-resource-id-size) = grok-resource-id(class-test-pointer); let text-address = \%+(pointer-address(class-test-pointer), class-resource-id-size); let text-resource-pointer :: = make(, address: pointer-address-16(text-address)); let (text-resource, text-size) = grok-resource-id(text-resource-pointer); let creation-data-pointer = make(, address: \%+(text-address, text-size)); let data-size = grok-creation-data(creation-data-pointer); let template-size = as(, // skip overflow check \%-(\%+(pointer-address(creation-data-pointer), data-size), pointer-address(template))); resource-size(control) := template-size; values(template-size, control) end method grok-item-template; define method grok-resource-id (p :: ) => (id :: type-union(, ), resource-id-size :: ) let word-pointer = as(, p); let word-value = pointer-value(word-pointer); case word-value = #x0000 => values(0, size-of()); word-value = #xFFFF => let id = pointer-value(word-pointer, index: 1); let sz = 2 * size-of(); values(id, sz); otherwise => grok-resource-string(p); end end method grok-resource-id; define method grok-resource-string (p :: ) => (string :: , resource-size :: ) let resource-string :: = make(, address: pointer-address(p)); //---*** (size(resource-string) + 1) * size-of(referenced-type(resource-string)); let resource-size :: = (size(resource-string) + 1) * 2; values(resource-string, resource-size) end method grok-resource-string; define method grok-creation-data (p :: ) => (data-size :: ) let data-value :: = as(, pointer-value(p)); if (data-value = 0) size-of() else size-of() + data-value end end method grok-creation-data; /*---*** Not ready for prime-time yet! define constant $RT-TOOLBAR = MAKEINTRESOURCE(241); define C-struct slot Version-value :: ; slot Width-value :: ; slot Height-value :: ; slot ItemCount-value :: ; pointer-type-name: ; end C-struct ; struct CToolBarData { WORD wVersion; WORD wWidth; WORD wHeight; WORD wItemCount; //WORD aItems[wItemCount] WORD* items() { return (WORD*)(this+1); } }; *grok-resource-table*[$RT-TOOLBAR] := grok-toolbar; */