65.9K
CodeProject 正在变化。 阅读更多。
Home

以编程方式更改 Windows XP 的 Bootskin、Windows 文件保护和主题

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (25投票s)

2008 年 12 月 18 日

CPOL

13分钟阅读

viewsIcon

62832

Vistra 代码演练文章,解释 Windows 的一些有趣之处。

引言

我在网上搜索东西时,看到一个软件可以修改 Windows XP 的 bootskin。我感到惊讶,并开始探索第三方软件(非微软)如何实现这一点。这就是 Vistra 开发的开始。这是一篇 Vistra 代码演练文章,解释 Windows 的一些有趣之处。在本文结束时,您将了解什么是 Windows 文件保护,如何更改 Windows XP 的 bootskin,如何以编程方式更改 Windows 主题,以及其他一些内容。

什么是 Vistra?

Vistra 这个名字既不是商标也不是版权。为了给 Vista 一个相对的名字,它被称为 Vistra(或 VISual TRAnsformation)。Vistra 是一款小型软件,可以更改您的 Windows XP 主题为 Vista 的外观,使您的任务栏/开始菜单透明,使用您自己的图片更改 bootskin,为您的桌面壁纸设置定时器,并使您的驱动器图标看起来像 Vista 的驱动器图标。

除了 bootskin 代码,我认为其他代码都很熟悉,您可以在网上轻松找到。我没有团队来测试 Vistra;我只是开发了它并将其发布在我的网站上。请注意,这款软件并非该领域任何其他现有软件的竞争对手。

故事讲够了,我们开始吧。

在解释如何以编程方式更改 bootskin 或应用主题之前,让我们先看一下使 Vistra 正常工作的几个函数。

资源安全

如果您编辑了 Vistra 的公司名称/产品名称/文件描述/法律版权或其他任何资源信息,则无法使用 Vistra。这并不意味着 Vistra 100% 防止损坏,但它在一定程度上是安全的。让我们看看如何实现。

bool CVISTRADlg::IsProductValid()
{
      TCHAR szName[MAX_PATH+1];
      GetModuleFileName(NULL,szName,MAX_PATH);
      HANDLE hFile;
      if ((hFile = CreateFile(szName, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL)) != INVALID_HANDLE_VALUE)     
      {
            DWORD dwSize=GetFileSize(hFile,0);
            CloseHandle(hFile);
            if(dwSize!=2154496 )          //2,154,496 bytes
            {
                  return false;
            }

            CString sInfo=GetFileInfo(_T("CompanyName"));
            sInfo=Crypt(sInfo,12);
            if(sInfo.Compare(_T("ZN`T^1`XYaWd]`[h"))!=0) return false;

            sInfo=GetFileInfo(_T("InternalName"));
            sInfo=Crypt(sInfo,13);
            if(sInfo.Compare(_T("cw‚dƒsAy{"))!=0) return false;

            sInfo=GetFileInfo(_T("ProductName"));
            sInfo=Crypt(sInfo,14);
            if(sInfo.Compare(_T("dxƒe„t"))!=0) return false;

            sInfo=GetFileInfo(_T("FileDescription"));
            sInfo=Crypt(sInfo,15);
            if(sInfo.Compare(_T("_‚€ˆ|xz‰7n‚}=r‡…އODg•–œI–•›ZO¤£“¡§¥—") 
              _T("©§´\\±­_”¢µ®¦¦¸g©·®kŸÁ¯ÁÄqŸ¸ÂÊ‚w®ÂÍϽ}ÂÑÉ×ǃÍÈÕÕÛ—˜™"))!=0)
                        return false;

            sInfo=GetFileInfo(_T("LegalCopyright"));
            sInfo=Crypt(sInfo,16);
            if(sInfo.Compare(_T("BABK4=y@8g[mak>mefndqjmhuWJKm™šO¢š™›¨¨V©¬Ÿ­²¢¢m"))!=0) 
                        return false;
      }
      return true;
}

我相信上面的代码非常直观。

检查点 1:使用 GetModuleFileName() 获取当前模块的文件名,获取文件的句柄,并获取当前模块的文件大小。如果大小不同,即不等于您软件的实际大小,则提示为无效产品。

检查点 2:获取公司名称/产品名称/文件描述/法律版权等的资源值,并与加密的实际值进行比较。如果任何一项不同,则提示为无效产品。

Crypt() 是一个简单的加密函数,用于加密资源值。在代码中加密资源值的原因是,当您在十六进制/二进制编辑器中打开产品时,您可以找到产品中使用的字符串,并可以使用该编辑器直接编辑所有字符串。例如,上述函数中使用了 _T(“CompanyName”)。在十六进制/二进制编辑器中打开 Vitsra 并搜索 CompanyName,您就可以找到它并可以编辑它。此外,您还可以找到 _T("ZN`T^1`XYaWd]`[h"),这是实际的公司名称,但已加密。它无法理解,所以您无法编辑它。即使您将其编辑为您自己的,它也无法与代码中进行的加密比较匹配。因此,产品将无法启动。

将您的 Crypt() 函数返回一个整数值而不是字符串值,使其更复杂并减少损坏的可能性。例如:

CString sInfo=GetFileInfo(_T("CompanyName"));
unsigned long nCryptValue=Crypt(sInfo,12);
if(nCryptValue!=6854266) return false;

GetFileInfo() 函数用于从您软件的资源中检索信息,如公司名称/产品名称/文件描述/法律版权等。

CString GetFileInfo(CString sInfo)
{
      TCHAR szName[MAX_PATH+1];
      GetModuleFileName(NULL,szName,MAX_PATH);
      CString strInfo(_T(""));
      TCHAR* pstrVerInfo = NULL;
      DWORD VerInfoLen, imsi;
      UINT imsi2;
      LPVOID pstrInfo;
      VerInfoLen = GetFileVersionInfoSize( szName, &imsi );
      pstrVerInfo = new TCHAR[VerInfoLen];
      if( ::GetFileVersionInfo( szName,(DWORD )0, VerInfoLen,(LPVOID )pstrVerInfo ) )
      {
            TCHAR szPath[MAX_PATH];
            _stprintf(szPath,_T("\\StringFileInfo\\040904e4\\%s"),sInfo);
            if( VerQueryValue( pstrVerInfo,szPath, (LPVOID*)&pstrInfo, &imsi2 ) )
                  strInfo = (TCHAR*)pstrInfo;
      }
      delete pstrVerInfo;
      pstrVerInfo = NULL;
      return strInfo;
}

040904e4 是您的块头值。在 Visual Studio(我使用的是 2005)中打开项目的资源选项卡 -> 版本信息以查找块头值。

如何设置窗口透明度

要设置窗口的透明度,请使用 user32.dllSetLayeredWindowAttributes 函数,这是一个从 Windows 2000 起引入的著名 API。

void SetTransparency(HWND hwnd, UINT nAlpha)
{
      HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL"));
      typedef BOOL (WINAPI *lpfn) (HWND hWnd, COLORREF cr,BYTE bAlpha, DWORD dwFlags);
      lpfn pSetLayeredWindowAttributes;   
      pSetLayeredWindowAttributes = 
        (lpfn)GetProcAddress(hUser32,"SetLayeredWindowAttributes");
      if(!pSetLayeredWindowAttributes) return;
      LONG nStyle=GetWindowLong(hwnd, GWL_EXSTYLE);
      if(( nStyle | 0x00080000)!=nStyle) 
            ::SetWindowLong(hwnd, GWL_EXSTYLE, nStyle ^ 0x00080000);
      pSetLayeredWindowAttributes(hwnd, 0,(BYTE) nAlpha, 0x00000002);
      ::RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
}

其中 hwnd 必须是一个顶层窗口,而 nAlpha 的值可以是从 0 到 255。

上述函数是安全的,如果您在 Windows 2000 之前的操作系统上运行产品,您不会收到 API 或 DLL 未找到的错误。而且,我建议使用 GetModuleHandle()GetProcAddress() 来调用新引入的 API 以支持旧操作系统。

如何以编程方式设置桌面壁纸

要以编程方式设置桌面壁纸,您必须创建一个 IActiveDesktop 接口的实例。使用创建的实例,您可以访问 Active Desktop 对象 的各种函数。有关 Active Desktop 对象 的更多信息,请参阅 MSDN。

int SetWallpaper(LPCTSTR szPath)
{
      CoInitialize ( NULL );
      USES_CONVERSION;
      HRESULT hr;
      IActiveDesktop* pIAD;
      hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, 
             CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**) &pIAD );
      if ( SUCCEEDED(hr) )
      {
            hr = pIAD->SetWallpaper (T2W(szPath),0);
            hr = pIAD->ApplyChanges(AD_APPLY_ALL);
            pIAD->Release();
      }
      if ( FAILED(hr) ) return 0;
      CoUninitialize();
      return 1;
}

如何以编程方式重启计算机

简单地调用带有 EWX_REBOOTExitWindowsEx() 可能会因您的进程所提供的访问权限不足而无法重启计算机。以下函数将为当前进程授予重启计算机的访问权限。

BOOL RestartComputer()
{
   HANDLE hToken; 
   TOKEN_PRIVILEGES tkp; 
   // Get a token for this process. 
   if (!OpenProcessToken(GetCurrentProcess(), 
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
      return( FALSE ); 
   // Get the LUID for the shutdown privilege. 
   LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); 
   tkp.PrivilegeCount = 1;  // one privilege to set    
   tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
   // Get the shutdown privilege for this process. 
   AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,(PTOKEN_PRIVILEGES)NULL, 0); 
   if (GetLastError() != ERROR_SUCCESS) 
      return FALSE; 
   // Shut down the system and force all applications to close. 
   if (!ExitWindowsEx(EWX_REBOOT | EWX_FORCE,0) )
      return FALSE; 
   return TRUE;
}

如何获取文件版本

下面是一个非常直接的代码,它使用 GetFileVersionInfo 来获取给定文件的版本信息。

bool GetFileVersion(LPCTSTR lpszFilePath, DWORD &dwMajor, 
     DWORD &dwMinor, DWORD &dwSubMinor, DWORD &dwBuild)
{
      DWORD dwDummy;
      bool bResult=false;
      DWORD dwFVISize = GetFileVersionInfoSize( lpszFilePath , &dwDummy ); 
      LPBYTE lpVersionInfo = new BYTE[dwFVISize]; 
      VS_FIXEDFILEINFO *lpFfi;
      bResult=GetFileVersionInfo( lpszFilePath , 0 , dwFVISize , 
                                  lpVersionInfo )?true:false; 
      if(!bResult){ delete []lpVersionInfo; return false;}
      UINT uLen; 
      bResult=VerQueryValue(lpVersionInfo , _T(“\\”) , 
                 (LPVOID *)&lpFfi , &uLen )?true:false; 
      DWORD dwFileVersionMS = lpFfi->dwFileVersionMS; 
      DWORD dwFileVersionLS = lpFfi->dwFileVersionLS; 
      delete [] lpVersionInfo; 
      dwMajor=HIWORD(dwFileVersionMS);
      dwMinor=LOWORD(dwFileVersionMS);
      dwSubMinor=HIWORD(dwFileVersionLS);
      dwBuild=LOWORD(dwFileVersionLS);
      return bResult;
}

检查当前操作系统

由于 Vistra 只能在 Windows XP 上运行,所以我使用了以下函数来检查 Vistra 正在运行的当前操作系统。

bool IsWinXP()
{
      OSVERSIONINFO osver;
      memset(&osver, 0, sizeof(OSVERSIONINFO));
      osver.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
      if(!GetVersionEx( &osver ))
            return false;
      if(osver.dwPlatformId == VER_PLATFORM_WIN32_NT && 
         osver.dwMajorVersion == 5 && osver.dwMinorVersion>=1 )
            return true;
      return false;
}
操作系统 版本号
Windows Server 2008 6.0
Windows Vista 6.0
Windows Server 2003 R2 5.2
Windows Server 2003 5.2
Windows XP 5.1
Windows 2000 5.0

更改驱动器图标

在“Software\Microsoft\Windows\CurrentVersion\Explorer”下创建一个名为 DriveIcons 的注册表项(如果不存在)。

HKEY hKey;

DWORD dwError=RegCreateKeyEx(HKEY_LOCAL_MACHINE,
  _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DriveIcons"),
0,0,0,KEY_ALL_ACCESS,NULL,&hKey,0);

if(dwError!=ERROR_SUCCESS) return false;

为要更改图标的驱动器创建一个子项。例如,对于 C: 驱动器,创建一个名为“C”的子项。

为驱动器项创建一个名为“DefaultIcon”的子项。

dwError=RegCreateKeyEx(hKey,_T("C"),0,0,0, 
               KEY_ALL_ACCESS,0,&hKey,0);          // DriveIcons\C

if(dwError!=ERROR_SUCCESS) return false;

dwError=RegCreateKeyEx(hKey,_T("DefaultIcon"),0,0,0,
                  KEY_ALL_ACCESS,0,&hKey,0);      // DriveIcons\C\DefaultIcon

if(dwError!=ERROR_SUCCESS) return false;

在“DefaultIcon”项的默认值中设置一个图标路径。我所有的图标都在 Vistra 本身里面,所以我将 Vistra EXE 路径和图标索引设置为图标路径。

if(dwError==ERROR_SUCCESS)
{
    TCHAR szName[MAX_PATH+1];
    GetModuleFileName(NULL,szName,MAX_PATH);
    CString sFilename;
    sFilename.Format(_T("%s,%d"),szName,nIndex);
    RegSetValueEx(hDriveKey,_T(""),0,REG_EXPAND_SZ, 
         (BYTE *)sFilename.GetString(),sFilename.GetLength()*sizeof(TCHAR));
}

使用 GetDriveType() 函数来确定驱动器的类型并相应地设置图标。

int nType=::GetDriveType(dr); 
if (nType > DRIVE_NO_ROOT_DIR )
{
      // Set icon here
      // nType==DRIVE_CDROM
      // nType==DRIVE_FIXED
      // nType==DRIVE_REMOVABLE
      // etc.,
}

警告:下面解释的代码将修改您的操作系统文件。此处提供的所有代码仅用于知识目的。如果您尝试下面的任何代码,请确保您了解您正在做什么。我不对可能对您的操作系统造成的任何损害负责。我建议您安装 Microsoft Virtual PC 并在您的 Virtual PC 中尝试所有操作。

如果您曾经在网上搜索过更改 Windows XP bootskin 的代码,那么您应该会遇到一个名为 ntoskrnl.exe 的文件。正如已经警告过的,这是一个系统文件(实际上是您的操作系统内核),您将修改此可执行文件中的一些字节,以便在启动时获得您自己的 bootskin。因此,在编写和测试代码时要小心。请务必备份原始的 ntoskrnl.exe 文件。在任何时候,如果您搞砸了什么,请使用 Windows 系统还原点,通过安全模式进行恢复。

Windows 文件保护 (WFP)

以下是您将在本主题中了解到的内容:

  • 为什么要了解所有这些?
  • 什么是 Windows 文件保护?
  • 什么是 sfc.dllsfc_os.dll
  • 什么是序数(ordinal number)?
  • 如何使用 SetSfcFileException() 禁用 WFP
  • 如何使用 SfcTerminateWatcherThread() 禁用 WFP
  • 如何通过注册表禁用 WFP

为什么要了解所有这些?

Ntoskrnl.exe 是一个受 WFP 保护的系统文件。因此,在受保护的情况下,您无法修改该可执行文件。一旦 WFP 监视器收到受保护文件的更改通知,它就会将已更改的文件替换为原始文件。

什么是 Windows 文件保护?

Windows 文件保护 (WFP) 可防止程序替换关键的 Windows 系统文件。程序不得覆盖这些文件,因为它们被操作系统和其他程序使用。保护这些文件可以防止程序和操作系统出现问题。

有关更多信息,请参阅:Windows 文件保护功能的说明

什么是 sfc.dll 和 sfc_os.dll?

SFC 代表系统文件检查器。SFC.exe 是一个应用程序,它将扫描 Windows 受保护的文件是否有更改,并在文件被更改时将其替换为原始文件。sfc.dll 是一个包装器库,它将一些函数调用重定向到 sfc_os.dllsfc.dll 也有一些自己的函数。Sfc_os.dll 是具有保护和取消保护、枚举受保护文件等的函数的库。

sfc_os.dll 中的函数

  • SfcGetNextProtectedFile()
  • SfcIsFileProtected()
  • SfcWLEventLogoff()
  • SfcWLEventLogon()

sfc.dll 中的函数(所有上述函数的包装器)

  • SRSetRestorePoint()
  • SRSetRestorePointA()
  • SRSetRestorePointW()
  • SfpVerifyFile()

sfc_os.dll 中大约有 11 个函数。只有四个有名称。其他函数没有信息,只能通过它们的序数来引用。

什么是序数?

序数只是 DLL 中函数的唯一标识符。

如何使用 SetSfcFileException() 禁用 WFP

下面解释的 DisableWindowsFileProtection() 函数通过其序数 5 调用 sfc_os.dll 中的 SetSfcFileException() 函数。调用 SetSfcFileException() 后,您将有一分钟的时间来编辑文件。如果成功,返回值为 0;如果发生错误或文件未受保护,则返回 1。

bool DisableWindowsFileProtection(LPCTSTR szFilename)
{
  USES_CONVERSION;
  TCHAR szSystemDir[MAX_PATH+1];
  int nSize=GetSystemDirectory(szSystemDir,MAX_PATH);
  szSystemDir[nSize]='\0';
  TCHAR szSFCOS[MAX_PATH+1];
  _tcscpy(szSFCOS,szSystemDir);
  _tcscat(szSFCOS,_T("\\sfc_os.dll"));
  HMODULE hSFSModule=::LoadLibrary(szSFCOS);
  if(!hSFSModule) return false;
  typedef DWORD (__stdcall *SETSFCFILEEXCEPTION) 
           (DWORD dwReserved, PWCHAR dwPath, DWORD dwParam2);
  SETSFCFILEEXCEPTION pFnSetSfcFileException;
  pFnSetSfcFileException= 
    (SETSFCFILEEXCEPTION) GetProcAddress(hSFSModule,(LPCSTR)5);
  if(pFnSetSfcFileException)
  {
        pFnSetSfcFileException(0,T2W(szFilename),-1);
  }
  else
  {
        ::FreeLibrary(hSFSModule);
        return false;
  }
  ::FreeLibrary(hSFSModule);
  return true;
}

如何使用 SfcTerminateWatcherThread() 禁用 WFP

SfcTerminateWatcherThread() 的名称本身就说明了它的作用。此函数调用将终止监视器线程,并且在下次重启之前,WFP 将不起作用。该函数没有参数,调用起来非常简单。但是,使用此函数有一个陷阱;函数调用必须来自启动监视器线程的进程,即 Winlogon.exe。只需研究如何将代码注入另一个进程,我相信 Robert Kuster 的文章将足够有用:将代码注入另一个进程的三种方法

如何通过注册表禁用 WFP

创建并设置以下注册表值:

  • 键:HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows NT\Windows File Protection
  • 名称:SFCDisable
  • 值:0xFFFFFF9D

我想这已经足够多了。我们的目标只是禁用 WFP 几秒钟。因此,SfcFileException() 调用就足够了。

请记住,禁用 WFP 是非常危险的。它应该重新启用,否则任何病毒/恶意软件都可能影响您的系统文件。

用您自己的图片修改默认 bootskin

为什么我要改变我的 bootskin,当它已经看起来很好了?为什么我要冒这么大的风险修改一个简单的 bootskin,它对我来说什么都不会做?上面两个问题的答案很简单。作为一个普通用户,不要尝试这个,您的问题是正确的。作为一个开发者,尝试一下,但要小心,这样您将学到一些您从未遇到过的事情。

暂时将您的 ntoskrnl.exe 复制到桌面或其他您想要的位置。打开您的 Visual Studio(我使用的是 2005),转到“文件->打开->文件…”。在文件打开对话框中,选择“文件类型”“*.dll 或 *.exe”。选择您复制的 ntoskrnl 文件。完成了吗?您应该能看到 ntoskrnl.exe 的资源文件。打开“位图”并选择 ID 为“1”的位图。这就是您的默认 bootskin 图像。难以置信,对吧?一个全黑的图像在启动时会带有 Windows 徽标和文字?打开编号为“8”的“位图”。同样的黑色图像,但更小,这是您在 bootskin 上看到的进度条。那些实际的图像在哪里?它们是否隐藏在这些黑色图像后面?部分是的。这可能有点难理解,也有点枯燥,只有当您不熟悉调色板时。好的,在继续之前,请打开编号为“5”的“位图”。惊讶吗?这就是启动图像吗?但它不是。所有三个位图之间都有美丽的关系。

我不想再让您感到困惑了。简单来说,1 和 8 分别是您的启动皮肤和进度皮肤,而 5 是包含 1 和 8 的调色板信息的图像。由于 1 和 8 没有调色板信息,您看不到图像,它们也显示为黑色。要正确查看这两个图像,您需要将位图 5 的调色板信息应用到 1 和 8。但是,您仍然无法获得实际图像,但您可以看到不同颜色的图像。为什么会这样?这是因为位图 5 中的调色板信息已被修改,我们可以称之为加密,调色板中的每种颜色都向上移动了一个级别。所以当使用这个调色板时,我们需要将整个颜色向下移动一步才能获得原始调色板。如果我们这样做,最后一个颜色会怎么样?是的,我们将丢失最后一个颜色。所以调色板表中只有 15 种颜色,而不是 16 种。

在继续之前,我想告诉您一件重要的事情,Windows XP 的 bootskin 图像应该是 640x480x16 分辨率。即,640x480 大小的 16 色位图。因此,当您选择一个位图文件来替换 bootskin 时,请确保它是 640x480 且为 16 色。Vistra 可以处理这种情况,即它会将给定的位图转换为 640x480x16。但是我使用了一些第三方的位图转换代码,这里不提供这些代码。

我在这里提供了整个代码来更改 Windows XP 的 bootskin。下面的代码不使用调色板或其他图像处理技术。我直接打开了 ntoskrnl.exe 并搜索了调色板模式,然后用我自己的模式替换了它。ReplaceBootSkin 函数将接收 szFilename(应为 ntoskrnl.exe)、szSkinFile(您的图像文件)和 nSkinReplacentoskrnl.exe 中的资源 ID)作为参数。SavePalette() 将保存给定调色板的信息并清除 bootskin 的调色板信息。RewritePalette() 函数将在给定的 szFilenamentoskrnl.exe)中搜索一个字节模式(调色板字节 00 00 00 00 15 1A 20 00),并用保存的调色板信息(来自 SavePalette)替换所有连续的 64 字节(调色板大小)。因此,您无需担心调色板的移位、从位图 5 抓取调色板信息等。如果您能用更好的方式通过调色板实现相同的功能,我将不胜感激。

牢记以上几点,并在此处暂停。我们是否错过了什么?我们禁用了 WFP,并且知道如何用我们自己的位图文件替换 ntoskrnl.exe 中的默认 bootskin 位图。我们还需要了解什么?如何使用我们自己的位图文件更新可执行文件中的资源位图?您以前做过吗?

是的。我们还有最后一步。这里是 BeginUpdateResource()UpdateResource()EndUpdateResource() API。参考 MSDN 或下面的代码来理解这些 API 的作用。简单来说,BeginUpdateResource() 将获取一个资源句柄用于更新。UpdateResource() 将用给定的新资源数据更新与句柄关联的资源。EndUpdateResource() 将关闭资源句柄。

文件中的位图与资源中的位图之间存在差异。UpdateResource() 只需要来自资源的位图句柄。区别在于位图文件头;资源位图没有位图文件头信息。因此,您可以在 ReplaceBootSkin() 函数中注意到,代码将首先删除位图文件头信息。

void SavePalette(LPBYTE pData, LPBYTE palette)
{
      BYTE byte[64];
      memcpy(byte,palette,64);
      memcpy(palette,pData+0x28,64);
      memcpy(pData+0x28,byte,64);
}


bool RewritePalette(CString sFilename, LPBYTE palette)
{
      BYTE find[8];
      find[0]=0x0;find[1]=0x0;find[2]=0x0;find[3]=0x0;
      find[4]=0x15;find[5]=0x1A;find[6]=0x20;find[7]=0x0;
      BYTE read[8];
      memset(read,0,8);
      BYTE pal[64];
      memcpy(pal,palette,64);
      CString sTmp=sFilename+_T(".tmp");
      FILE *fp=_tfopen(sFilename,_T("rb"));
      if(!fp) return false;
      FILE *ofp=_tfopen(sTmp,_T("wb"));
      if(!ofp) {fclose(fp);return false;}
      int i=0;
      while(!feof(fp))
      {
            read[i]=fgetc(fp);
            if(i==7)
            {
                  i=0;
                  if(memcmp(find,read,8)==0)
                  {
                        fwrite((void *)palette,sizeof(BYTE),64,ofp);
                        // 8 bytes already read using fgetc()
                        fread((void *)palette,sizeof(BYTE),64-8,fp); 
                  }
                  else
                  {
                        fwrite((void *)read,sizeof(BYTE),8,ofp);
                  }
            }
            else i++;
      }
      if(i>1)
      {
            fwrite((void *)read,sizeof(BYTE),i,ofp);
      }
      fclose(fp);
      fclose(ofp);
      ::DeleteFile(sFilename);
      ::rename(sTmp,sFilename);
      return true;
}

bool ReplaceBootSkin(LPCTSTR szFilename, LPCTSTR szSkinFile, UINT nSkinReplace)
{
      HANDLE hUpdateRes;  // update resource handle 
      DWORD dwSize=0;
      HANDLE hFile;
      if ((hFile = CreateFile(sFilename, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL)) == INVALID_HANDLE_VALUE)     return false;
       DWORD dwBitmapSize = GetFileSize(hFile, NULL) - sizeof(BITMAPFILEHEADER);     
      BITMAPFILEHEADER    bmfHeader;
      DWORD dwRead;
      if (!ReadFile(hFile, (LPSTR)&bmfHeader, 
                 sizeof (BITMAPFILEHEADER),&dwRead, NULL)) 
      {
            CloseHandle(hFile);
            return false;
      }

      if (sizeof (BITMAPFILEHEADER) != dwRead || bmfHeader.bfType != 0x4d42) 
      {
            CloseHandle(hFile);
            return false;
      }
     
      HGLOBAL hGlobal=GlobalAlloc(GMEM_MOVEABLE,dwBitmapSize);
      if(!hGlobal) 
      {
            CloseHandle(hFile);
            return false;
      }

      LPVOID bmpData=GlobalLock(hGlobal);
      if(!ReadFile(hFile, bmpData, dwBitmapSize,&dwRead,NULL))
      {
            GlobalUnlock(hGlobal);
            GlobalFree(hGlobal);
            CloseHandle(hFile); 
            return false;
      }
      CloseHandle(hFile);

      BYTE palette[64];
      memset(palette,0,64);
      SavePalette((LPBYTE)bmpData, palette, false);

      hUpdateRes = BeginUpdateResource(szFilename, FALSE); 
      if (hUpdateRes == NULL) 
      {
            GlobalUnlock(hGlobal);
            GlobalFree(hGlobal);
            return false;
      }
 
      if(!UpdateResource
      (     
            hUpdateRes, 
            RT_BITMAP, 
            MAKEINTRESOURCE(nSkinReplace), 
            MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 
            bmpData, 
            dwBitmapSize
      )) 
      { 
            GlobalUnlock(hGlobal);
            GlobalFree(hGlobal);
            return false;
      } 

      if (!EndUpdateResource(hUpdateRes, FALSE)) 
      { 
            GlobalUnlock(hGlobal);
            GlobalFree(hGlobal);
            return false;
      } 
      GlobalUnlock(hGlobal);
      GlobalFree(hGlobal);
      RewritePalette(szFilename,palette);
      return true;
}

以编程方式设置视觉样式(主题)

Uxtheme.dll 是您的视觉样式器,负责处理所有主题功能。uxtheme.dll 的一些函数没有名称,只能通过它们的序数来引用。SetVisualStyle() 函数就是其中之一。它的序数是 65。使用此函数可以以编程方式设置主题。

bool SetVisualStyle(LPCTSTR szVistraTheme)
{
      TCHAR szUxTheme[MAX_PATH+1];
      UINT nSize=::GetSystemDirectory(szUxTheme,MAX_PATH);
      szUxTheme[nSize]='\0';
      _tcscat(szUxTheme,_T("\\uxtheme.dll"));
      HMODULE hModule=::LoadLibrary(szUxTheme);
      if(!hModule) return false;
      typedef int (__stdcall *SETVISUALSTYLE) (LPCWSTR szTheme, 
                   LPCWSTR szScheme, LPCWSTR szFontType, int nReserved);
      USES_CONVERSION;  
      SETVISUALSTYLE pFnSetVisualStyle;
      pFnSetVisualStyle=(SETVISUALSTYLE) 
              GetProcAddress(hModule,MAKEINTRESOURCE(LOWORD(65)));
      if(pFnSetVisualStyle)
      {
            pFnSetVisualStyle(T2W(szVistraTheme), 
                  L"NormalColor",L"NormalSize",1|32);
      }
      ::FreeLibrary(hModule);
      return true;
}

结论

我不会在此文章中提供示例项目。原因是,我不希望本文的读者仅仅下载并进行试错,或者注释和取消注释代码,因为本文中的某些代码如果处理不当会有些危险。我再次提醒您,本文仅供知识参考,如果您想尝试实验代码,请在“MS Virtual PC”或“VMware”中进行。您可以创建一个对话框应用程序,并将文章中提供的代码复制粘贴到其中以查看输出。我请求您在尝试运行代码之前理解代码。与其称之为文章,我更愿意称之为我在 Vistra 开发过程中获得的经验,并与大家分享。谢谢。

© . All rights reserved.