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






4.82/5 (25投票s)
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.dll 的 SetLayeredWindowAttributes
函数,这是一个从 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_REBOOT
的 ExitWindowsEx()
可能会因您的进程所提供的访问权限不足而无法重启计算机。以下函数将为当前进程授予重启计算机的访问权限。
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.dll 和 sfc_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.dll。sfc.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
(您的图像文件)和 nSkinReplace
(ntoskrnl.exe 中的资源 ID)作为参数。SavePalette()
将保存给定调色板的信息并清除 bootskin 的调色板信息。RewritePalette()
函数将在给定的 szFilename
(ntoskrnl.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 开发过程中获得的经验,并与大家分享。谢谢。