Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., CTO at artsy.net, fun at playplay.io, NYC

Email Twitter LinkedIn Github

I accidentally broke support for Windows 95/98/ME in dotNetInstaller. I was even more surprised that someone cared. A developer reported the bug, and then my friends in Botswana are saying that Windows 95/98/ME is alive and well all over the place. How can I say no to African children?!

The first problem is locating a Windows 98 CD in the pile of old MSDN distributions, creating a boot disk image in an .IMZ format, converting it to .IMG with WinImage and finally making a VM. Fixing the display driver was a pain. Phew, I now have a booting and running Windows 98 to test after half a day of travelling back 10 years of software development.

MSLU stands for Microsoft Layer for Unicode. Making MSLU work involves not upgrading to Visual Studio 2008 (sigh) and doing a few tricks described in an excellent article in MSDN. I ran all C++ unit tests and fixed those that didn’t run on Windows 98 (a couple of Win32 API calls only). Good.

Carrying unicows.dll is annoying, this is a setup bootstrapper. So I tried embedding it as a resource (RES_UNICOWS CUSTOM "unicows.dll") and am now decompressing it at runtime to %TEMP%\unicows.dll. It works beautifully!

The complete code is here.

static HMODULE SaveAndLoadMSLU()
{
  char tf[MAX_PATH] = { 0 };
  if (! GetTempPathA(MAX_PATH, tf))
    return NULL;
  if (0 != strcat_s<>(tf, "unicows.dll"))
    return NULL;

  if (GetFileAttributesA(tf) == INVALID_FILE_ATTRIBUTES)
  {
    HRSRC res_unicows = ::FindResourceA(NULL, "RES_UNICOWS", "CUSTOM");
    if (res_unicows == NULL)
      return NULL;

    HGLOBAL hgl = ::LoadResource(NULL, res_unicows);
    if (hgl == NULL)
      return NULL;

    DWORD size = SizeofResource(NULL, res_unicows);
    if (size == 0)
      return NULL;

    LPVOID buffer = LockResource(hgl);

    HANDLE h = CreateFileA(tf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (h == INVALID_HANDLE_VALUE)
      return NULL;

    DWORD written = 0;
    if (! WriteFile(h, buffer, size, & written, NULL))
    {
      ::CloseHandle(h);
      return NULL;
    }

    ::CloseHandle(h);
  }

  return ::LoadLibraryA(tf);
}

static HMODULE __stdcall LoadMSLU (void)
{
  HMODULE hUnicows = ::LoadLibraryA("unicows.dll");
  if (hUnicows == 0) hUnicows = SaveAndLoadMSLU();
  if (hUnicows == 0)
  {
    MessageBoxA(0, "Error loading unicows.dll (Microsoft Layer for Unicode, MSLU)", "dotNetInstaller", MB_ICONSTOP | MB_OK);
    _exit(-1);
  }
  return hUnicows;
}

// load Microsoft Layer for Unicode (MSLU)
// http://msdn.microsoft.com/en-us/magazine/cc301794.aspx
extern "C" HMODULE (__stdcall *_PfnLoadUnicows) (void) = & LoadMSLU;