Search code examples
c++winapiembedded-resource

Replacing Bitmap Resource in exe


I have a program created using WinAPI. Within the program, I embedded a bitmap as a resource and the program loads that resource and displays it as a background image via bitblt.

Below, I created a console program to replace the background in the WinAPI program. It successfully replaces the bitmap but now the WinAPI program does not display a background anymore. I know the replacement worked because using ResourceHacker, I can click on the resource and it display it fine.

This screenshot shows that it was replaced successfully: enter image description here

However, if I use ResourceHacker to save the image back to the disk, the image cannot be previewed or opened with any editors:

enter image description here

If I use resourcehacker to replace the image within the WinAPI program, it works just fine and the program displays it as the background.

With all that being said, can anyone explain what I did wrong below?

//In my resource file of the WINAPI PROGRAM:
//IDI_ICON        ICON                "Resources/Icon.ico"
//IDB_BACKGROUND    BITMAP  DISCARDABLE "Resources/BackgroundImg.bmp"

#include <windows.h>
#include <iostream>
#include <fstream>
#include <vector>

using namespace std;

bool Update(int ResourceID, std::string ModulePath, string FilePath)
{
    HANDLE hResource = BeginUpdateResource(ModulePath.c_str(), false);

    if (hResource != nullptr)
    {
        std::fstream File(FilePath.c_str(), std::ios::in | std::ios::binary);
        if (File.is_open())
        {
            File.seekg(0, std::ios::end);
            std::size_t FileSize = File.tellg();
            File.seekg(0, std::ios::beg);
            std::vector<std::uint8_t> Data(FileSize);  //Also used a pointer.. makes no difference..
            File.read(reinterpret_cast<char*>(Data.data()), FileSize);
            File.close();

            if (UpdateResource(hResource, RT_BITMAP, MAKEINTRESOURCE(ResourceID), MAKELANGID(0x0409, 0x1), Data.data(), FileSize))
            {
                EndUpdateResource(hResource, false);
                return true;
            }
        }
    }
    return false;
}

int main()
{
    if (Update(1001, "Module.exe", "Resources/BackgroundImg.bmp"))
    {
        std::cout<<"Updated Successfully";
    }
    else
    {
        std::cout<<"Failed To Update";
    }
    return 0;
}

Solution

  • GAAHHH! I solved it! Just for anyone reading, this is the answer.. It's an old kb-article on Microsoft's support: http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b67883

    Now if you don't understand it:

    It says that the only difference between a bitmap from file and a bitmap stored as a resource is that one is a DIB and the other is a packed DIB. The difference between these two is that one has the BITMAPFILEHEADER and the other does not. The resources are stored without the FileHeaders so they must be removed.

    Thus, when updating a bitmap resource, you must REMOVE the BitmapFileHeader (notice the SizeOf offset below):

    bool UpdateBitmap(int ResourceID, std::string ModulePath, string FilePath)
    {
        HANDLE hResource = BeginUpdateResource(ModulePath.c_str(), false);
    
        if (hResource != nullptr)
        {
            std::fstream File(FilePath.c_str(), std::ios::in | std::ios::binary);
            if (File.is_open())
            {
                File.seekg(0, std::ios::end);
                std::size_t FileSize = File.tellg();
                File.seekg(0, std::ios::beg);
                std::vector<std::uint8_t> Data(FileSize);
                File.read(reinterpret_cast<char*>(Data.data()), FileSize);
                File.close();
    
                if (UpdateResource(hResource, RT_BITMAP, MAKEINTRESOURCE(ResourceID), MAKELANGID(0x0409, 0x1), Data.data() + sizeof(BITMAPFILEHEADER), FileSize - sizeof(BITMAPFILEHEADER))) //Notice the sizeof.
                {
                    EndUpdateResource(hResource, false);
                    return true;
                }
            }
        }
        return false;
    }
    

    The only difference between this code and the one in the OP is the offset by sizeof(BITMAPFILEHEADER).