Application Modernization: What Is It and How to Get Started
At some moment in the last month, I needed to create an .ICO file on the fly with a couple images inside, preferable it was code in C#, The .NET framework 2.0 only supports HICON; that, basically, is one icon with just a single image in it. I was frustrated when I found no Icon Editor with source code; the only thing I found was closed commercial products charging from $19 to $39 and not exposing APIs at all. So, the only solution was to create my own library capable of creating and parsing ICO files.
I believe in open-source code and I though I could help the developer community by sharing this knowledge. Also open-source pushes companies and commercial products to go farther.
After the work was done, I read about ICL files (Icon Libraries), those can contain many icons inside a file, and I decided to support that too. The same happened with EXE/DLLs, but I still decided to support Windows Vista Icons. All this was really hard work and a lot of headaches because there is no much information exposed, so I spent a lot of time reversing-engineering and researching over the net. I hope it can be as useful for you as it is for me.
Note: As with every new project, many things can happen. Not every case can be tested and many things you don't see until after they are tested. It is a very fresh project; if there is something that doesn't work or you think should work differently than it does, before give your vote write and give me the chance to fix it. In this way, we both get the benefit of having more stable code and creating a more complete library; and, at the same time, you get my thanks if that helps.
Also, I included one libraries as sample. I borrowed some icons from Windows Vista; for the 256x256 versions I put a watermark because the icons has copyright ownership. Hopefully, I won't have trouble with that.
The objective of the library is to create an abstraction layer from the different file formats and provide an interface to allow icon modification without the hassle of knowing internal file formats.
Formats Currently Supported
- ICO: Read and write icons with different image sizes and depths
- ICL: Read and write icons inside the icon library
- DLL: Read DLLs and export to a new DLL
- EXE: Import supported
- OCX: Import supported
- CPL: Import supported
- SRC: Import supported
Iconlib exposes three different objects:
- MultiIcon: The only object that can be instantiated in the library. Once a MultiIcon object is created, it exposes APIs to load and save in the file system or streams with standard formats as ICO/ICL/DLL.
- SingleIcon: A single icon inside MultiIcon; it allows you to add/remove images in it.
- IconImage: A single image inside SingleIcon; at this point, IconImage exposes the icon lowest resources like the XOR (Image) and the AND Image (Mask). Also, it exposes an Icon property that will basically construct a .NET Icon created from the XOR and AND image from where you can get a HICON handle.
As you can see, there is a hierarchical structure. Basically, a MultiIcon contains Icons and an Icon contains Images.
Library Objects Diagram
The library contains many classes and structs but only exposes the three important classes;that's all the developer needs to control the complete behavior of the library. The rest are all internal; many classes/structs and methods are not safe to be exposed to the developer. For that reason, I recommend keeping IconLib as a separated project because, if the developer incorporates the source in his/her project, all the internal classes/structs/methods will be exposed and probably could not be used properly.
I cannot give support for the library when it is not used the way it was designed. I'm providing the source-code as a nice gesture because I believe in open-source code. I hope you will make good use of it without ripping off the source from where it belongs.
Before I started IconLib, I had no clue about how icons work, but was not before I found an excellent article. Although it is out of date with the coming of Windows Vista icons, it is very precise in explaining how Icons format files work.
Something to watch for: On my first version, I followed the icon format details, but the library could not load some of the icons I was testing. When I went deep to the bytes I noticed that much information was missing from the directory entry.
I tested those Icons with another product and I could see that, for example, one popular product had no problem opening this kind of icon; that is because every icon directory entry points to a ICONIMAGE struct. This ICONIMAGE struct has a BITMAPINFOHEADER struct that contains more information than the icon directory entry itself, so basically, by using the information from the BITMAPINFOHEADER, I could reconstruct the information in the directory entry.
The same rule cannot be applied to Windows Vista Icons because those images doesn't contain a BITMAPINFOHEADER struct anymore. So, if some information is missing from the directory entry, the icon image becomes invalid.
Anyway, reconstructing the icon directory entry is a plus and discarding icon images not properly constructed is acceptable, No company should provide icons with missing information in the headers.
NE Format (ICL)
The NE Format is the popular format to store icon libraries. This format originality was used for Executables on the 16-bit version of Windows.
You can get more information for NE format from the Microsoft web site.
This was the more challenging part of the project. When I started to research about ICL, I have no clue that these were 16-bit DLLs. I couldn't find any data about this extension; a couple days later, I almost dropped the project, but I read someplace that ICLs are 16-bit with resources inside. My quest to recover resources from a 16-bit DLL started. So far, my only next objective was trying to load a 16-bit DLL in memory; of course, at first I tried to load the library with the standard Win32API LoadLibrary, LoadLibraryEx, but it failed with:
193 - ERROR_BAD_EXE_FORMAT (Is not a valid application.)
I'm no expert in Kernel memory allocation, but I guess it's because in Win32 the memory is protected between applications and in 16-bit it is not. So, when I tried to allocation memory for 16-bit, the OS rejected the operation.
The next step was trying to load the ICL (16-bit DLL) in memory using just 16 bits APIs. If you read the MSDN WIN32 API, the only API left for 16-bit is Loadmodule. When I tried to use it, it loaded the library but immediately Windows starts to give strange message boxes, such as "Not enough memory to run 16-bit applications" or things like that. I wrote in MS forums and other forums, but couldn't find anything really helpful about how I could get those resources. At that time, it was very clear that I could not load a 16-bit DLL in memory and that I needed to create my own NE parser/'linker'.
The Microsoft article about NE Format (New Executable) is an excellent source. It describes, in detail, every field in the file.
A NE format file starts with an IMAGE_DOS_HEADER. This header is there to keep compatibility with MS-DOS OS. This header also contains some specific fields to indicate the existence of a newly segmented file format. IMAGE_DOS_HEADER usually contains a valid executable program to run on MS-DOS. This program is called a stub program and usually it just prints the message 'This program cannot run on MS-DOS' on the screen.
After you read the IMAGE_DOS_HEADER, the first thing to do is to know whether this is a valid header. Usually, every file contains what is called a Magic Number because the data store in that field is not relevant to the program but it contains a signature to describe the type of the file.
You can find Magic Numbers almost everywhere. The magic number for the IMAGE_DOS_HEADER is 0x5A4D; this represent the chars 'MZ', and it stands by 'Mark Zbikowski'. He is a Microsoft Architect and started to work with MS a few years after its inception. Probably, he could never think that his signature was going to appear thousands of times in almost every personal computer in the world. If the magic number is 'MZ', the only extra field you care about is the e_lfanew. This header is the offset to the new EXE header, NE Header.
You seek in the file for this offset, and then at this point, you read a new header. The header is IMAGE_OS2_HEADER and it contains all information about the program to be loaded in memory.
The first thing to do is to load the magic number again, but this time the magic number must be 0x454E; it means 'NE'. If the signature matches, you can continue analyzing the rest of the headers. At this point, the more important field is ne_rsrctab; this field contains the offset of the resource table. This offset is the number of bytes you have to jump from the beginning of this header to be in position to read the resource table.
If everything went well, you are ready to read the resource table.
The first field of the Resource Table is the align shift. Usually, you find the explanation as 'The alignment shift count for resource data. When the shift count is used as an exponent of 2, the resulting value specifies the factor, in bytes, for computing the location of a resource in the executable file.'
In my own words, this field was tricky to understand how it works. It was created for compatibility with MS-DOS, and it will contain the multiply factor necessary to reach the resource. As you will see, the resource offset is a variably type ushort; this means that it can address only 64 Kb (65536). Actually, almost every file is bigger than that, and here is where the 'alignment shift' field comes into play. Alignment shift is an ushort and 'usually' it is in the range of 2 to 10. This number is the number of times you have to shift the number 1 to the left.
For example, an alignment shift of 5 means 1 << 5, which is equal to 32.
An alignment shift of 10 means 1 << 10 = 1024.
Now, with the virtual offset address from the resource table, you multiply for the result shift value and you get the real offset address in the file. For example, if the resource is located at the virtual address 0x2000 and the alignment shift is 5, you get the following:
Realoffset = (1 << 5) * 0x2000
Realoffset = 32 * 0x2000
Realoffset = 0x40000
The real offset of this resource is at 262144 (0x40000).
Wow, this is cool, right? Because you just use a ushort, you can locate a resource at any position. Okay, now you wonder, where is the trick?
The trick is, for example, if you use a shift alignment of 5; that means the minimum addressable space is 32 bytes (1 << 5). This means that, if you want to allocate 10 bytes with this method, 32 bytes will be allocated and just the first 10 will be used; another 22 bytes will be wasted.
Now, if you take the shift alignment as 0, you won't waste space because the virtual address will match with the real address. It is not so easy, and that works only if the resource is located in the range of the first 64Kb space. So to make it clear, this shift alignment is directly proportional to the file size.
The next table shows the maximum file sizes you can get with different shift alignments:
(1 << 0) * (2 ^ 16) = 64KB
(1 << 1) * (2 ^ 16) = 128KB
(1 << 2) * (2 ^ 16) = 256KB
(1 << 3) * (2 ^ 16) = 512KB
(1 << 4) * (2 ^ 16) = 1MB
(1 << 5) * (2 ^ 16) = 2MB
(1 << 6) * (2 ^ 16) = 4MB
(1 << 7) * (2 ^ 16) = 8MB
(1 << 8) * (2 ^ 16) = 16MB
(1 << 9) * (2 ^ 16) = 32MB
(1 << 10) * (2 ^ 16) = 64MB
Calculating this value is not so easy. IconLib at first used a shift factor of 9 because I thought that 32 Mb was more than enough for an Icon library. But, I was surprised when I extracted Windows Vista DLLs and IconLib got out of range for some files. I incremented the shift factor to 10; then, I could dump the content of the Windows Vista DLL in an ICL file, but it took 63 Mb.
A factor of ten allows you to create an ICL library up to 64 Mb but every resource will address at minimum 1024 bytes. If you think that's not bad because all resources will be bigger than 1024, it is not so easy. A factor of ten means that it can address in multiples of 1024; if the resource is 1025, it will allocate 2048 bytes in the file system.
In conclusion, with a factor of 10, IconLib is wasting an average of (1024 / 2) 512 bytes by resource allocated, but at the same time it lets you create an Icon Library with 64 Mb. My next release will predict the max file size and adjust the shift factor dynamically; it is not an easy task if you want to predict the number without scanning the memory to know the max space to be addressed, especially for PNG images where this value is dynamic too. Hopefully, the shift alignment field is clear now and you can come back to the resource table.
The next field is an array of TYPEINFO.
TYPEINFO is a struct that gives you information about the resource. There are many types of resource that can be allocated, but IconLib just in interested in two types, RT_GROUP_ICON and RT_ICON.
When IconLib reads the TYPEINFO array, it discards all structs where rtTypeID is not RT_GROUP_ICON or RT_ICON.
The RT_GROUP_ICON type gives you information about a icon.
RT_ICON type gives you information about a single image inside the icon.
rtResourceCount is the number of resources of this type in the executable.
rtNameInfo is an array of TNAMEINFO containing the information about every resource of this type. The length of this array is equal to rtResourceCount.
Here is where you have the information about the resource itself; the rnOffset is the virtual address where the physical resource is located. To know the real address, see how alignment shift works above.
The rnLength is the length of the resource on a virtual address space. This means if, for example, the resource has a length of 1500 bytes and the alignment shift is 10, the value on this field will be 2. The way to calculate the length is:
rnLenght = Ceiling(realresourcesize / (1 << resource_table.rscAlignShift));
rnFlags tell us if the resource is fixed, preloaded, or shareable
rnID is the ID of the resource.
rnHandle is reserved.
rnUsage is reserved.
Going back to TYPEINFO, if the TYPEINFO struct type is RT_GROUP_ICON, you read the array of TNAMEINFO that gives you information about every icon in the resource.
The offset in every TNAMEINFO will contain a pointer to a GRPICONDIR struct. This struct will tell information about a single icon, like how many images it contains and one array of GRPICONDIRENTRY, when every in GRPICONDIRENTRY contains information about the image, like width, height, colorcount, and so forth. Now if the TYPEINFO struct type is RT_ICON, you read the array of TNAMEINFO that gives you the information about every single image inside the resource.
Going back to the Resource Table, you have another three fields: rscEndTypes, rscResourcesNames, and rscEndNames.
rscEndTypes is a ushort value that tells you when to stop reading for TYPEINFO structs. The resource table struct doesn't tell how many TYPEINFO structs it contains, so the only way to know is with a stopper flag. This flag is rscEndTypes; if, when you read TYPEINFO the two first bytes are zero, this means you have reached the end of the TYPEINFO array.
rscResourceNames is an array of bytes with the names of every resource in TYPEINFO struct. The names (if any) associated with the resources are in this table. Each name is stored as consecutive bytes; the first byte specifies the number of characters in the name. For example if the array is [5, 73, 67, 79, 78, 48, 5, 73, 67, 79, 78, 49], this is translated like an array of two string 'Icon1', 'Icon2'.
[5, 73, 67, 79, 78, 48, 5, 73, 67, 79, 78, 49]
[73, 67, 79, 78, 48] = 'Icon1'
[73, 67, 79, 78, 49] = 'Icon2'
If you wonder when you have to stop reading for bytes in the array, for this another stopper flag rscEndNames with a value of zero exists. When the bytes have been read, if a null ('\x0') character is detected, the process must stop reading the names, and they are ready to be translated as ANSI strings.
At this point, you already have all the information and binary data for the Icons and the images inside the Icon.