mirror of
https://github.com/chibicitiberiu/rainmeter-studio.git
synced 2024-02-24 04:33:31 +00:00
1410 lines
35 KiB
C++
1410 lines
35 KiB
C++
/*
|
|
Copyright (C) 2012 Birunthan Mohanathas
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
#include "SkinInstaller.h"
|
|
#include "DialogInstall.h"
|
|
#include "resource.h"
|
|
#include "../Version.h"
|
|
|
|
#define WM_DELAYED_CLOSE WM_APP + 0
|
|
|
|
extern GlobalData g_Data;
|
|
|
|
DialogInstall* DialogInstall::c_Dialog = nullptr;
|
|
|
|
inline bool IsWin32Build()
|
|
{
|
|
#ifdef _WIN64
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
** Constructor.
|
|
**
|
|
*/
|
|
DialogInstall::DialogInstall(HWND wnd, const WCHAR* file) : Dialog(wnd),
|
|
m_TabInstall(wnd),
|
|
m_HeaderBitmap(),
|
|
m_InstallThread(),
|
|
m_PackageUnzFile(),
|
|
m_PackageFileName(file),
|
|
m_PackageFormat(PackageFormat::Old),
|
|
m_BackupPackage(false),
|
|
m_BackupSkins(true),
|
|
m_MergeSkins(false),
|
|
m_SystemFonts(false)
|
|
{
|
|
}
|
|
|
|
/*
|
|
** Destructor.
|
|
**
|
|
*/
|
|
DialogInstall::~DialogInstall()
|
|
{
|
|
if (m_PackageUnzFile)
|
|
{
|
|
unzClose(m_PackageUnzFile);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Creates the dialog.
|
|
**
|
|
*/
|
|
void DialogInstall::Create(HINSTANCE hInstance, LPWSTR lpCmdLine)
|
|
{
|
|
// Prompt to select .rmskin file if needed
|
|
WCHAR buffer[MAX_PATH];
|
|
if (!*lpCmdLine)
|
|
{
|
|
buffer[0] = L'\0';
|
|
|
|
OPENFILENAME ofn = {0};
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.lpstrFilter = L"Rainmeter skin file (.rmskin)\0*.rmskin;*.zip";
|
|
ofn.nFilterIndex = 1;
|
|
ofn.lpstrFile = buffer;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrTitle = L"Select Rainmeter skin file";
|
|
ofn.lpstrDefExt = L"rmskin";
|
|
ofn.Flags = OFN_FILEMUSTEXIST;
|
|
|
|
if (!GetOpenFileName(&ofn))
|
|
{
|
|
return;
|
|
}
|
|
|
|
lpCmdLine = buffer;
|
|
}
|
|
|
|
HANDLE hMutex;
|
|
if (IsRunning(L"Rainmeter Skin Installer", &hMutex))
|
|
{
|
|
HWND hwnd = FindWindow(L"#32770", L"Rainmeter Skin Installer");
|
|
SetForegroundWindow(hwnd);
|
|
}
|
|
else
|
|
{
|
|
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_INSTALL_DIALOG), nullptr, (DLGPROC)DlgProc, (LPARAM)lpCmdLine);
|
|
ReleaseMutex(hMutex);
|
|
}
|
|
}
|
|
|
|
Dialog::Tab& DialogInstall::GetActiveTab()
|
|
{
|
|
return m_TabInstall;
|
|
}
|
|
|
|
INT_PTR CALLBACK DialogInstall::DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (!c_Dialog)
|
|
{
|
|
if (uMsg == WM_INITDIALOG)
|
|
{
|
|
c_Dialog = new DialogInstall(hWnd, (const WCHAR*)lParam);
|
|
return c_Dialog->OnInitDialog(wParam, lParam);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_COMMAND:
|
|
return c_Dialog->OnCommand(wParam, lParam);
|
|
|
|
case WM_CLOSE:
|
|
if (!c_Dialog->m_InstallThread)
|
|
{
|
|
EndDialog(hWnd, 0);
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_DESTROY:
|
|
delete c_Dialog;
|
|
c_Dialog = nullptr;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
INT_PTR DialogInstall::OnInitDialog(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HICON hIcon = (HICON)LoadImage(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_SKININSTALLER), IMAGE_ICON, 16, 16, LR_SHARED);
|
|
SendMessage(m_Window, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
|
|
if (GetOSPlatform() >= OSPLATFORM_VISTA)
|
|
{
|
|
SetDialogFont();
|
|
}
|
|
|
|
HWND item = GetDlgItem(m_Window, IDC_INSTALL_ADVANCED_BUTTON);
|
|
Dialog::SetMenuButton(item);
|
|
|
|
if (ReadPackage())
|
|
{
|
|
item = GetDlgItem(m_Window, IDC_INSTALL_HEADER_BITMAP);
|
|
if (m_HeaderBitmap)
|
|
{
|
|
SendMessage(item, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)m_HeaderBitmap);
|
|
}
|
|
else
|
|
{
|
|
RECT r;
|
|
GetClientRect(item, &r);
|
|
ShowWindow(item, SW_HIDE);
|
|
int yDiff = r.bottom;
|
|
|
|
// Move all controls on the main dialog up to "fill" header area.
|
|
int controlIds[] = { IDC_INSTALL_TAB, IDC_INSTALL_ADVANCED_BUTTON, IDC_INSTALL_INSTALL_BUTTON, IDCANCEL, 0 };
|
|
for (int i = 0; i < _countof(controlIds); ++i)
|
|
{
|
|
HWND control = controlIds[i] ? GetDlgItem(m_Window, controlIds[i]) : m_TabInstall.GetWindow();
|
|
GetWindowRect(control, &r);
|
|
MapWindowPoints(nullptr, m_Window, (POINT*)&r, sizeof(RECT) / sizeof(POINT));
|
|
MoveWindow(control, r.left, r.top - yDiff, r.right - r.left, r.bottom - r.top, TRUE);
|
|
}
|
|
|
|
// Remove blank area at the bottom of the dialog and center it.
|
|
GetWindowRect(m_Window, &r);
|
|
MoveWindow(m_Window, r.left, r.top + (yDiff / 2), r.right - r.left, r.bottom - r.top - yDiff, TRUE);
|
|
}
|
|
|
|
m_TabInstall.Activate();
|
|
}
|
|
else
|
|
{
|
|
if (m_ErrorMessage.empty())
|
|
{
|
|
m_ErrorMessage = L"Invalid package:\n";
|
|
m_ErrorMessage += PathFindFileName(m_PackageFileName.c_str());
|
|
m_ErrorMessage += L"\n\nThe Skin Packager tool must be used to create valid .rmskin packages.";
|
|
}
|
|
|
|
MessageBox(nullptr, m_ErrorMessage.c_str(), L"Rainmeter Skin Installer", MB_ERROR);
|
|
EndDialog(m_Window, 0);
|
|
}
|
|
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
INT_PTR DialogInstall::OnCommand(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDC_INSTALL_ADVANCED_BUTTON:
|
|
{
|
|
RECT r;
|
|
GetWindowRect((HWND)lParam, &r);
|
|
HMENU menu = LoadMenu(GetInstanceHandle(), MAKEINTRESOURCE(IDR_INSTALL_MENU));
|
|
HMENU subMenu = GetSubMenu(menu, 0);
|
|
|
|
if (m_PackageSkins.empty() || m_MergeSkins || m_BackupPackage)
|
|
{
|
|
EnableMenuItem(subMenu, IDM_INSTALL_BACKUPSKINS, MF_BYCOMMAND | MF_GRAYED);
|
|
}
|
|
else
|
|
{
|
|
CheckMenuItem(subMenu, IDM_INSTALL_BACKUPSKINS, (m_BackupSkins ? MF_CHECKED : MF_UNCHECKED) | MF_BYCOMMAND);
|
|
}
|
|
|
|
if (m_PackageFonts.empty())
|
|
{
|
|
EnableMenuItem(subMenu, IDM_INSTALL_SYSTEMFONTS, MF_BYCOMMAND | MF_GRAYED);
|
|
}
|
|
else
|
|
{
|
|
CheckMenuItem(subMenu, IDM_INSTALL_SYSTEMFONTS, (m_SystemFonts ? MF_CHECKED : MF_UNCHECKED) | MF_BYCOMMAND);
|
|
}
|
|
|
|
const WCHAR* formatName = m_PackageFormat == PackageFormat::New ? L"New format" : L"Old format";
|
|
ModifyMenu(subMenu, IDM_INSTALL_FORMAT, MF_STRING | MF_GRAYED | MF_BYCOMMAND, IDM_INSTALL_FORMAT, formatName);
|
|
|
|
TrackPopupMenu(
|
|
subMenu,
|
|
TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
r.left,
|
|
--r.bottom,
|
|
0,
|
|
m_Window,
|
|
nullptr);
|
|
|
|
DestroyMenu(menu);
|
|
}
|
|
break;
|
|
|
|
case IDC_INSTALL_INSTALL_BUTTON:
|
|
BeginInstall();
|
|
break;
|
|
|
|
case IDCANCEL:
|
|
if (!m_InstallThread)
|
|
{
|
|
EndDialog(m_Window, 0);
|
|
}
|
|
break;
|
|
|
|
case IDM_INSTALL_BACKUPSKINS:
|
|
m_BackupSkins = !m_BackupSkins;
|
|
break;
|
|
|
|
case IDM_INSTALL_SYSTEMFONTS:
|
|
m_SystemFonts = !m_SystemFonts;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
INT_PTR DialogInstall::OnNotify(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LPNMHDR nm = (LPNMHDR)lParam;
|
|
switch (nm->code)
|
|
{
|
|
case BCN_DROPDOWN:
|
|
{
|
|
NMHDR* hdr = &((NMBCDROPDOWN*)lParam)->hdr;
|
|
|
|
// Unpush the drop-down button part and simulate click
|
|
Button_SetDropDownState(hdr->hwndFrom, FALSE);
|
|
SendMessage(hdr->hwndFrom, BM_CLICK, 0, 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool DialogInstall::ExtractCurrentFile(const std::wstring& fileName)
|
|
{
|
|
// Some archives don't explicity list directories, so create them recursively
|
|
if (!CreateDirectoryRecursive(fileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (fileName.back() == L'\\')
|
|
{
|
|
// Nothing left to do
|
|
return true;
|
|
}
|
|
|
|
if (unzOpenCurrentFile(m_PackageUnzFile) != UNZ_OK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
HANDLE hFile = CreateFile(fileName.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int read;
|
|
do
|
|
{
|
|
BYTE buffer[16384];
|
|
DWORD written;
|
|
read = unzReadCurrentFile(m_PackageUnzFile, buffer, 16384);
|
|
if (read < 0 || !WriteFile(hFile, (LPCVOID)buffer, read, &written, nullptr) || read != written)
|
|
{
|
|
read = UNZ_ERRNO;
|
|
break;
|
|
}
|
|
}
|
|
while (read != UNZ_EOF);
|
|
|
|
CloseHandle(hFile);
|
|
|
|
return unzCloseCurrentFile(m_PackageUnzFile) == UNZ_OK && read == UNZ_EOF;
|
|
}
|
|
|
|
bool DialogInstall::ReadPackage()
|
|
{
|
|
const WCHAR* fileName = m_PackageFileName.c_str();
|
|
const WCHAR* fileExtension = PathFindExtension(fileName);
|
|
|
|
if (_wcsicmp(fileExtension, L".rmskin") == 0)
|
|
{
|
|
// Check if the footer is present (for new .rmskin format)
|
|
PackageFooter footer = {0};
|
|
|
|
FILE* file = _wfopen(fileName, L"rb");
|
|
__int64 fileSize = 0;
|
|
if (file)
|
|
{
|
|
fseek(file, -(long)sizeof(footer), SEEK_END);
|
|
fileSize = _ftelli64(file);
|
|
fread(&footer, sizeof(footer), 1, file);
|
|
fclose(file);
|
|
}
|
|
|
|
if (strcmp(footer.key, "RMSKIN") == 0)
|
|
{
|
|
m_PackageFormat = PackageFormat::New;
|
|
if (footer.size != fileSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (footer.flags)
|
|
{
|
|
m_BackupPackage = !(footer.flags & PackageFlag::Backup);
|
|
}
|
|
}
|
|
}
|
|
else if (_wcsicmp(fileExtension, L".zip") != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_PackageUnzFile = unzOpen(ConvertToAscii(fileName).c_str());
|
|
if (!m_PackageUnzFile)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
WCHAR buffer[MAX_PATH];
|
|
|
|
// Get temporary file to extract the options file and header bitmap
|
|
GetTempPath(MAX_PATH, buffer);
|
|
GetTempFileName(buffer, L"dat", 0, buffer);
|
|
std::wstring tempFile = buffer;
|
|
const WCHAR* tempFileSz = tempFile.c_str();
|
|
|
|
// Helper to sets buffer with current file name
|
|
auto getFileInfo = [&]()->bool
|
|
{
|
|
char cBuffer[MAX_PATH * 3];
|
|
unz_file_info ufi;
|
|
if (unzGetCurrentFileInfo(
|
|
m_PackageUnzFile, &ufi, cBuffer, _countof(cBuffer), nullptr, 0, nullptr, 0) == UNZ_OK)
|
|
{
|
|
const uLong ZIP_UTF8_FLAG = 1 << 11;
|
|
const DWORD codePage = (ufi.flag & ZIP_UTF8_FLAG) ? CP_UTF8 : CP_ACP;
|
|
MultiByteToWideChar(codePage, 0, cBuffer, strlen(cBuffer) + 1, buffer, MAX_PATH);
|
|
while (WCHAR* pos = wcschr(buffer, L'/')) *pos = L'\\';
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Loop through the contents of the archive until the settings file is found
|
|
WCHAR* path;
|
|
bool optionsFound = false;
|
|
do
|
|
{
|
|
if (!getFileInfo())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
path = wcsrchr(buffer, L'\\');
|
|
if (!path)
|
|
{
|
|
path = buffer;
|
|
}
|
|
else
|
|
{
|
|
if (m_PackageFormat == PackageFormat::New)
|
|
{
|
|
// New package files must be in root of archive
|
|
continue;
|
|
}
|
|
|
|
++path; // Skip slash
|
|
}
|
|
|
|
if (_wcsicmp(path, m_PackageFormat == PackageFormat::New ? L"RMSKIN.ini" : L"Rainstaller.cfg") == 0)
|
|
{
|
|
if (ExtractCurrentFile(tempFile))
|
|
{
|
|
optionsFound = ReadOptions(tempFileSz);
|
|
DeleteFile(tempFileSz);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
while (unzGoToNextFile(m_PackageUnzFile) == UNZ_OK);
|
|
|
|
if (!optionsFound)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Loop through the archive a second time and find included components
|
|
unzGoToFirstFile(m_PackageUnzFile);
|
|
|
|
m_PackageRoot.assign(buffer, path - buffer);
|
|
const WCHAR* root = m_PackageRoot.c_str();
|
|
do
|
|
{
|
|
if (!getFileInfo())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (wcsncmp(buffer, root, m_PackageRoot.length()) != 0)
|
|
{
|
|
// Ignore everything that isn't in the root directory
|
|
continue;
|
|
}
|
|
|
|
WCHAR* component = buffer + m_PackageRoot.length();
|
|
path = wcschr(component, L'\\');
|
|
if (path)
|
|
{
|
|
*path = L'\0';
|
|
++path;
|
|
}
|
|
else
|
|
{
|
|
if (_wcsicmp(component, m_PackageFormat == PackageFormat::New ? L"RMSKIN.bmp" : L"Rainstaller.bmp") == 0)
|
|
{
|
|
if (!ExtractCurrentFile(tempFile))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_HeaderBitmap = (HBITMAP)LoadImage(nullptr, tempFileSz, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
|
DeleteFile(tempFileSz);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
const WCHAR* pos = wcschr(path, L'\\');
|
|
const WCHAR* extension = PathFindExtension(pos ? pos : path);
|
|
if (pos)
|
|
{
|
|
// Component with subfolders
|
|
const std::wstring item(path, pos - path);
|
|
const WCHAR* itemSz = item.c_str();
|
|
|
|
if (_wcsicmp(component, L"Skins") == 0 &&
|
|
!IsIgnoredSkin(itemSz))
|
|
{
|
|
m_PackageSkins.insert(item);
|
|
}
|
|
else if (_wcsicmp(component, m_PackageFormat == PackageFormat::New ? L"Layouts" : L"Themes") == 0 &&
|
|
_wcsicmp(extension, m_PackageFormat == PackageFormat::New ? L".ini" : L".thm") == 0 &&
|
|
!IsIgnoredLayout(itemSz))
|
|
{
|
|
m_PackageLayouts.insert(item);
|
|
}
|
|
else if (_wcsicmp(component, L"Addons") == 0 &&
|
|
m_PackageFormat == PackageFormat::Old &&
|
|
!IsIgnoredAddon(itemSz))
|
|
{
|
|
m_PackageAddons.insert(item);
|
|
}
|
|
else if (_wcsicmp(component, L"Plugins") == 0 &&
|
|
_wcsicmp(itemSz, IsWin32Build() ? L"32bit" : L"64bit") == 0 &&
|
|
_wcsicmp(extension, L".dll") == 0 &&
|
|
!wcschr(pos + 1, L'\\'))
|
|
{
|
|
const std::wstring plugin(pos + 1);
|
|
if (!IsIgnoredPlugin(plugin.c_str()))
|
|
{
|
|
m_PackagePlugins.insert(plugin);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Component with subfiles
|
|
const std::wstring item = path;
|
|
const WCHAR* itemSz = item.c_str();
|
|
|
|
if (_wcsicmp(component, L"Fonts") == 0 &&
|
|
m_PackageFormat == PackageFormat::Old &&
|
|
_wcsicmp(extension, L".ttf") == 0)
|
|
{
|
|
m_PackageFonts.insert(item);
|
|
}
|
|
}
|
|
}
|
|
while (unzGoToNextFile(m_PackageUnzFile) == UNZ_OK);
|
|
|
|
if (m_PackageSkins.empty())
|
|
{
|
|
// Fonts can be installed only with skins
|
|
m_PackageFonts.clear();
|
|
}
|
|
|
|
return !(m_PackageSkins.empty() && m_PackageLayouts.empty() &&
|
|
m_PackageAddons.empty() && m_PackageFonts.empty() && m_PackagePlugins.empty());
|
|
}
|
|
|
|
bool DialogInstall::ReadOptions(const WCHAR* file)
|
|
{
|
|
WCHAR buffer[MAX_LINE_LENGTH];
|
|
|
|
const bool newFormat = m_PackageFormat == PackageFormat::New;
|
|
const WCHAR* section = newFormat ? L"rmskin" : L"Rainstaller";
|
|
|
|
const HWND window = m_TabInstall.GetWindow();
|
|
|
|
if (GetPrivateProfileString(section, L"Name", L"", buffer, 64, file) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
Static_SetText(GetDlgItem(window, IDC_INSTALLTAB_NAME_TEXT), buffer);
|
|
|
|
if (!newFormat)
|
|
{
|
|
// Determine if skins need to backed up based on name
|
|
int s;
|
|
int scanned = swscanf(buffer, L"Backup-%d.%d.%d-%d.%d.rmskin", &s, &s, &s, &s, &s);
|
|
m_BackupPackage = scanned == 5;
|
|
}
|
|
|
|
GetPrivateProfileString(section, L"Author", L"", buffer, 64, file);
|
|
Static_SetText(GetDlgItem(window, IDC_INSTALLTAB_AUTHOR_TEXT), buffer);
|
|
|
|
GetPrivateProfileString(section, L"Version", L"", buffer, 64, file);
|
|
Static_SetText(GetDlgItem(window, IDC_INSTALLTAB_VERSION_TEXT), buffer);
|
|
|
|
m_MergeSkins = GetPrivateProfileInt(section, newFormat ? L"MergeSkins" : L"Merge", 0, file) != 0;
|
|
|
|
GetPrivateProfileString(section, newFormat ? L"VariableFiles" : L"KeepVar", L"", buffer, MAX_LINE_LENGTH, file);
|
|
m_VariablesFiles = Tokenize(buffer, L"|");
|
|
|
|
if (GetPrivateProfileString(section, newFormat ? L"MinimumRainmeter" : L"MinRainmeterVer", L"", buffer, MAX_LINE_LENGTH, file) > 0)
|
|
{
|
|
std::wstring rainmeterDll = g_Data.programPath + L"Rainmeter.dll";
|
|
std::wstring rainmeterVersion = GetFileVersionString(rainmeterDll.c_str());
|
|
if (CompareVersions(buffer, rainmeterVersion) == 1)
|
|
{
|
|
m_ErrorMessage = L"Rainmeter ";
|
|
m_ErrorMessage += buffer;
|
|
m_ErrorMessage += L" or higher is required to install this package.\n\n"
|
|
L"Get the latest version from rainmeter.net and try again.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (GetPrivateProfileString(section, newFormat ? L"LoadType" : L"LaunchType", L"", buffer, MAX_LINE_LENGTH, file) > 0)
|
|
{
|
|
bool loadSkin = _wcsicmp(buffer, newFormat ? L"Skin" : L"Load") == 0;
|
|
|
|
GetPrivateProfileString(section, newFormat ? L"Load" : L"LaunchCommand", L"", buffer, MAX_LINE_LENGTH, file);
|
|
if (loadSkin)
|
|
{
|
|
if (newFormat)
|
|
{
|
|
m_LoadSkins.push_back(buffer);
|
|
}
|
|
else
|
|
{
|
|
m_LoadSkins = Tokenize(buffer, L"|");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_LoadLayout = buffer;
|
|
}
|
|
}
|
|
|
|
if (newFormat)
|
|
{
|
|
if (GetPrivateProfileString(section, L"MinimumDotNET", L"", buffer, MAX_LINE_LENGTH, file) > 0 &&
|
|
CompareVersions(buffer, GetDotNetVersionString()) == 1)
|
|
{
|
|
m_ErrorMessage = L".NET framework ";
|
|
m_ErrorMessage += buffer;
|
|
m_ErrorMessage += L" or higher is required to install this package.";
|
|
return false;
|
|
}
|
|
|
|
if (GetPrivateProfileString(section, L"MinimumWindows", L"", buffer, MAX_LINE_LENGTH, file) > 0 &&
|
|
CompareVersions(buffer, GetWindowsVersionString()) == 1)
|
|
{
|
|
m_ErrorMessage = L"Your version of Windows is not supported by this package.\n\n"
|
|
L"Contact the package author for more information.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DialogInstall::InstallPackage()
|
|
{
|
|
if ((!m_MergeSkins && m_BackupSkins) || m_BackupPackage)
|
|
{
|
|
// Move skins into backup folder
|
|
for (auto iter = m_PackageSkins.cbegin(); iter != m_PackageSkins.cend(); ++iter)
|
|
{
|
|
std::wstring from = g_Data.skinsPath + *iter;
|
|
if (_waccess(from.c_str(), 0) == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SHFILEOPSTRUCT fo =
|
|
{
|
|
nullptr,
|
|
FO_DELETE,
|
|
nullptr,
|
|
nullptr,
|
|
FOF_NO_UI | FOF_NOCONFIRMATION | FOF_ALLOWUNDO
|
|
};
|
|
|
|
if (m_BackupPackage)
|
|
{
|
|
// Remove current skin
|
|
from += L'\0';
|
|
fo.pFrom = from.c_str();
|
|
SHFileOperation(&fo);
|
|
}
|
|
else
|
|
{
|
|
std::wstring to = g_Data.skinsPath + L"@Backup\\";
|
|
CreateDirectory(to.c_str(), nullptr);
|
|
|
|
// Delete current backup
|
|
to += *iter;
|
|
to += L'\0';
|
|
fo.pFrom = to.c_str();
|
|
SHFileOperation(&fo);
|
|
|
|
if (!CopyFiles(from, to, true))
|
|
{
|
|
m_ErrorMessage = L"Unable to move to:\n";
|
|
m_ErrorMessage += to;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WCHAR buffer[MAX_PATH];
|
|
|
|
// Helper to sets buffer with current file name
|
|
auto getFileInfo = [&]()->bool
|
|
{
|
|
char cBuffer[MAX_PATH * 3];
|
|
unz_file_info ufi;
|
|
if (unzGetCurrentFileInfo(
|
|
m_PackageUnzFile, &ufi, cBuffer, _countof(cBuffer), nullptr, 0, nullptr, 0) == UNZ_OK)
|
|
{
|
|
const uLong ZIP_UTF8_FLAG = 1 << 11;
|
|
const DWORD codePage = (ufi.flag & ZIP_UTF8_FLAG) ? CP_UTF8 : CP_ACP;
|
|
MultiByteToWideChar(codePage, 0, cBuffer, strlen(cBuffer) + 1, buffer, MAX_PATH);
|
|
while (WCHAR* pos = wcschr(buffer, L'/')) *pos = L'\\';
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
unzGoToFirstFile(m_PackageUnzFile);
|
|
const WCHAR* root = m_PackageRoot.c_str();
|
|
do
|
|
{
|
|
if (!getFileInfo())
|
|
{
|
|
m_ErrorMessage = L"Error retrieving file info.";
|
|
return false;
|
|
}
|
|
|
|
if (wcsncmp(buffer, root, m_PackageRoot.length()) != 0)
|
|
{
|
|
// Ignore everything that isn't in the root directory
|
|
continue;
|
|
}
|
|
|
|
WCHAR* component = buffer + m_PackageRoot.length();
|
|
WCHAR* path = wcschr(component, L'\\');
|
|
if (path)
|
|
{
|
|
*path = L'\0';
|
|
++path;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool error = false;
|
|
std::wstring targetPath;
|
|
|
|
WCHAR* pos = wcschr(path, L'\\');
|
|
WCHAR* extension = PathFindExtension(pos ? pos : path);
|
|
if (pos)
|
|
{
|
|
const std::wstring item(path, pos - path);
|
|
|
|
if (_wcsicmp(component, L"Skins") == 0 &&
|
|
m_PackageSkins.find(item) != m_PackageSkins.end())
|
|
{
|
|
targetPath = g_Data.skinsPath;
|
|
}
|
|
else if (_wcsicmp(component, L"Addons") == 0 &&
|
|
m_PackageFormat == PackageFormat::Old &&
|
|
m_PackageAddons.find(item) != m_PackageAddons.end())
|
|
{
|
|
targetPath = g_Data.settingsPath;
|
|
targetPath += L"Addons\\";
|
|
}
|
|
else if (_wcsicmp(component, L"Plugins") == 0 &&
|
|
_wcsnicmp(path, IsWin32Build() ? L"32bit" : L"64bit", pos - path) == 0 &&
|
|
_wcsicmp(extension, L".dll") == 0 &&
|
|
!wcschr(pos + 1, L'\\'))
|
|
{
|
|
const std::wstring plugin(pos + 1);
|
|
if (m_PackagePlugins.find(plugin) != m_PackagePlugins.end())
|
|
{
|
|
path = pos + 1;
|
|
targetPath = g_Data.settingsPath;
|
|
targetPath += L"Plugins\\";
|
|
}
|
|
}
|
|
|
|
if (!targetPath.empty())
|
|
{
|
|
targetPath += path;
|
|
error = !ExtractCurrentFile(targetPath);
|
|
}
|
|
else if (_wcsicmp(component, m_PackageFormat == PackageFormat::New ? L"Layouts" : L"Themes") == 0 &&
|
|
_wcsicmp(extension, m_PackageFormat == PackageFormat::New ? L".ini" : L".thm") == 0 &&
|
|
m_PackageLayouts.find(item) != m_PackageLayouts.end())
|
|
{
|
|
if (m_PackageFormat == PackageFormat::Old)
|
|
{
|
|
wcscpy_s(extension, 5, L".ini");
|
|
}
|
|
|
|
targetPath = g_Data.settingsPath;
|
|
targetPath += L"Layouts\\";
|
|
targetPath += path;
|
|
error = !ExtractCurrentFile(targetPath);
|
|
if (!error)
|
|
{
|
|
CleanLayoutFile(targetPath.c_str());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_wcsicmp(component, L"Fonts") == 0 &&
|
|
m_PackageFormat == PackageFormat::Old &&
|
|
_wcsicmp(extension, L".ttf") == 0)
|
|
{
|
|
for (auto iter = m_PackageSkins.cbegin(); iter != m_PackageSkins.cend(); ++iter)
|
|
{
|
|
targetPath = g_Data.skinsPath;
|
|
targetPath += *iter;
|
|
targetPath += L"\\@Resources\\Fonts\\";
|
|
targetPath += path;
|
|
error = !ExtractCurrentFile(targetPath);
|
|
if (error)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
m_ErrorMessage = L"Unable to create file:\n";
|
|
m_ErrorMessage += targetPath;
|
|
m_ErrorMessage += L"\n\nSkin Installer will now quit.";
|
|
return false;
|
|
}
|
|
}
|
|
while (unzGoToNextFile(m_PackageUnzFile) == UNZ_OK);
|
|
|
|
if (!m_MergeSkins && m_BackupSkins)
|
|
{
|
|
KeepVariables();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DialogInstall::BeginInstall()
|
|
{
|
|
HWND item = GetDlgItem(m_Window, IDC_INSTALL_ADVANCED_BUTTON);
|
|
EnableWindow(item, FALSE);
|
|
|
|
item = GetDlgItem(m_Window, IDC_INSTALL_INSTALL_BUTTON);
|
|
EnableWindow(item, FALSE);
|
|
|
|
item = GetDlgItem(m_Window, IDCANCEL);
|
|
EnableWindow(item, FALSE);
|
|
|
|
item = GetDlgItem(m_TabInstall.GetWindow(), IDC_INSTALLTAB_THEME_CHECKBOX);
|
|
if (Button_GetCheck(item) == BST_UNCHECKED)
|
|
{
|
|
m_LoadLayout.clear();
|
|
m_LoadSkins.clear();
|
|
}
|
|
EnableWindow(item, FALSE);
|
|
|
|
item = GetDlgItem(m_TabInstall.GetWindow(), IDC_INSTALLTAB_COMPONENTS_LIST);
|
|
{
|
|
// Remove unchecked items from the component sets
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_GROUPID | LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = 0;
|
|
|
|
int itemCount = ListView_GetItemCount(item);
|
|
for (; lvi.iItem < itemCount; ++lvi.iItem)
|
|
{
|
|
ListView_GetItem(item, &lvi);
|
|
|
|
std::set<std::wstring>* component = nullptr;
|
|
switch (lvi.iGroupId)
|
|
{
|
|
case 0: component = &m_PackageSkins; break;
|
|
case 1: component = &m_PackageLayouts; break;
|
|
case 2: component = &m_PackageAddons; break;
|
|
case 3: component = &m_PackagePlugins; break;
|
|
}
|
|
|
|
BOOL checked = ListView_GetCheckState(item, lvi.iItem);
|
|
if (component && !checked)
|
|
{
|
|
component->erase(*(std::wstring*)lvi.lParam);
|
|
}
|
|
}
|
|
}
|
|
EnableWindow(item, FALSE);
|
|
|
|
m_InstallThread = (HANDLE)_beginthreadex(nullptr, 0, InstallThread, this, 0, nullptr);
|
|
if (!m_InstallThread)
|
|
{
|
|
MessageBox(m_Window, L"Unable to start install.", L"Rainmeter Skin Installer", MB_ERROR);
|
|
EndDialog(m_Window, 0);
|
|
}
|
|
}
|
|
|
|
UINT __stdcall DialogInstall::InstallThread(void* pParam)
|
|
{
|
|
DialogInstall* dialog = (DialogInstall*)pParam;
|
|
|
|
if (!CloseRainmeterIfActive())
|
|
{
|
|
MessageBox(dialog->m_Window, L"Unable to close Rainmeter.", L"Rainmeter Skin Installer", MB_ERROR);
|
|
}
|
|
else
|
|
{
|
|
HWND progressText = GetDlgItem(dialog->m_TabInstall.GetWindow(), IDC_INSTALLTAB_INPROGRESS_TEXT);
|
|
ShowWindow(progressText, SW_SHOWNORMAL);
|
|
|
|
HWND progressBar = GetDlgItem(dialog->m_TabInstall.GetWindow(), IDC_INSTALLTAB_PROGRESS);
|
|
ShowWindow(progressBar, SW_SHOWNORMAL);
|
|
SendMessage(progressBar, PBM_SETMARQUEE, (WPARAM)TRUE, 0);
|
|
|
|
if (!dialog->InstallPackage())
|
|
{
|
|
ShowWindow(progressText, SW_HIDE);
|
|
ShowWindow(progressBar, SW_HIDE);
|
|
|
|
if (dialog->m_ErrorMessage.empty())
|
|
{
|
|
dialog->m_ErrorMessage = L"Unknown error.";
|
|
}
|
|
dialog->m_ErrorMessage += L"\n\nClick OK to close Skin Installer.";
|
|
|
|
MessageBox(dialog->m_Window, dialog->m_ErrorMessage.c_str(), L"Rainmeter Skin Installer", MB_ERROR);
|
|
|
|
dialog->m_LoadSkins.clear();
|
|
dialog->m_LoadLayout.clear();
|
|
}
|
|
|
|
dialog->LaunchRainmeter();
|
|
}
|
|
|
|
EndDialog(dialog->GetWindow(), 0);
|
|
return 0;
|
|
}
|
|
|
|
void DialogInstall::KeepVariables()
|
|
{
|
|
WCHAR keyname[32767]; // Max size returned by GetPrivateProfileSection
|
|
WCHAR buffer[4];
|
|
std::wstring currKey, currValue;
|
|
|
|
for (int i = 0, isize = m_VariablesFiles.size(); i < isize; ++i)
|
|
{
|
|
std::wstring fromPath = g_Data.skinsPath + L"@Backup\\";
|
|
fromPath += m_VariablesFiles[i];
|
|
std::wstring toPath = g_Data.skinsPath + m_VariablesFiles[i];
|
|
|
|
unsigned int count = GetPrivateProfileSection(L"Variables", keyname, 32767, fromPath.c_str());
|
|
|
|
if ((_waccess(fromPath.c_str(), 0) == 0) && (_waccess(toPath.c_str(), 0) == 0)
|
|
&& (count > 0))
|
|
{
|
|
for (unsigned int j = 0; j < count; ++j)
|
|
{
|
|
if (keyname[j] == L'=')
|
|
{
|
|
if (GetPrivateProfileString(L"Variables", currKey.c_str(), nullptr, buffer, 4, toPath.c_str()) > 0)
|
|
{
|
|
while (keyname[++j] != L'\0') currValue += keyname[j];
|
|
WritePrivateProfileString(L"Variables", currKey.c_str(), currValue.c_str(), toPath.c_str());
|
|
currValue.clear();
|
|
}
|
|
else
|
|
{
|
|
while (keyname[j] != L'\0') ++j;
|
|
}
|
|
currKey.clear();
|
|
}
|
|
else
|
|
{
|
|
currKey += keyname[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DialogInstall::LaunchRainmeter()
|
|
{
|
|
// Execute Rainmeter and wait up to a minute for it process all messages
|
|
std::wstring rainmeterExe = g_Data.programPath + L"Rainmeter.exe";
|
|
std::wstring args;
|
|
if (!m_LoadLayout.empty())
|
|
{
|
|
args += L"!LoadLayout \"";
|
|
args += m_LoadLayout;
|
|
args += L'"';
|
|
}
|
|
|
|
SHELLEXECUTEINFO sei = {0};
|
|
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
|
sei.fMask = SEE_MASK_WAITFORINPUTIDLE | SEE_MASK_UNICODE;
|
|
sei.lpFile = rainmeterExe.c_str();
|
|
sei.lpParameters = args.c_str();
|
|
sei.lpDirectory = g_Data.programPath.c_str();
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
ShellExecuteEx(&sei);
|
|
|
|
if (!m_LoadSkins.empty())
|
|
{
|
|
std::wstring::size_type pos;
|
|
std::wstring bang;
|
|
|
|
for (int i = 0, isize = (int)m_LoadSkins.size(); i < isize; ++i)
|
|
{
|
|
const std::wstring& skinName = m_LoadSkins[i];
|
|
pos = skinName.find_last_of(L"\\");
|
|
if (pos != std::wstring::npos)
|
|
{
|
|
// Append with [!ActivateConfig "Config" "File.ini"]
|
|
bang += L"[!ActivateConfig \"";
|
|
bang.append(skinName, 0, pos);
|
|
bang += L"\" \"";;
|
|
bang.append(skinName, pos + 1, skinName.length() - pos + 1);
|
|
bang += L"\"]";
|
|
}
|
|
}
|
|
|
|
if (!bang.empty())
|
|
{
|
|
sei.fMask = SEE_MASK_UNICODE;
|
|
sei.lpParameters = (LPCTSTR)bang.c_str();
|
|
ShellExecuteEx(&sei);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DialogInstall::CleanLayoutFile(const WCHAR* file)
|
|
{
|
|
// Clear the [Rainmeter] section.
|
|
WritePrivateProfileSection(L"Rainmeter", L"", file);
|
|
|
|
// Remove the UseD2D key from all sections.
|
|
WCHAR buffer[4096];
|
|
if (GetPrivateProfileSectionNames(buffer, _countof(buffer), file) > 0)
|
|
{
|
|
const WCHAR* section = buffer;
|
|
size_t sectionLength = 0;
|
|
while ((sectionLength = wcslen(section)) > 0)
|
|
{
|
|
WritePrivateProfileString(section, L"UseD2D", nullptr, file);
|
|
section += sectionLength + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper for the IsIgnore... functions.
|
|
bool IsIgnoredName(const WCHAR* name, const WCHAR* names[], int namesCount)
|
|
{
|
|
for (int i = 0; i < namesCount; ++i)
|
|
{
|
|
if (_wcsicmp(name, names[i]) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DialogInstall::IsIgnoredSkin(const WCHAR* name)
|
|
{
|
|
static const WCHAR* s_Skins[] =
|
|
{
|
|
L"Backup",
|
|
L"@Backup"
|
|
};
|
|
|
|
return IsIgnoredName(name, s_Skins, _countof(s_Skins));
|
|
}
|
|
|
|
bool DialogInstall::IsIgnoredLayout(const WCHAR* name)
|
|
{
|
|
static const WCHAR* s_Layouts[] =
|
|
{
|
|
L"Backup",
|
|
L"@Backup"
|
|
};
|
|
|
|
return IsIgnoredName(name, s_Layouts, _countof(s_Layouts));
|
|
}
|
|
|
|
bool DialogInstall::IsIgnoredAddon(const WCHAR* name)
|
|
{
|
|
static const WCHAR* s_Addons[] =
|
|
{
|
|
L"Backup",
|
|
L"Rainstaller",
|
|
L"RainBackup"
|
|
};
|
|
|
|
return IsIgnoredName(name, s_Addons, _countof(s_Addons));
|
|
}
|
|
|
|
bool DialogInstall::IsIgnoredPlugin(const WCHAR* name)
|
|
{
|
|
static const WCHAR* s_Plugins[] =
|
|
{
|
|
L"AdvancedCPU.dll",
|
|
L"CoreTemp.dll",
|
|
L"FileView.dll",
|
|
L"FolderInfo.dll",
|
|
L"InputText.dll",
|
|
L"iTunesPlugin.dll",
|
|
L"MediaKey.dll",
|
|
L"NowPlaying.dll",
|
|
L"PerfMon.dll",
|
|
L"PingPlugin.dll",
|
|
L"PowerPlugin.dll",
|
|
L"Process.dll",
|
|
L"QuotePlugin.dll",
|
|
L"RecycleManager.dll",
|
|
L"ResMon.dll",
|
|
L"SpeedFanPlugin.dll",
|
|
L"SysInfo.dll",
|
|
L"VirtualDesktops.dll",
|
|
L"WebParser.dll",
|
|
L"WifiStatus.dll",
|
|
L"Win7AudioPlugin.dll",
|
|
L"WindowMessagePlugin.dll"
|
|
};
|
|
|
|
return IsIgnoredName(name, s_Plugins, _countof(s_Plugins));
|
|
}
|
|
|
|
/*
|
|
** Splits the string from the delimiters and trims whitespace.
|
|
*/
|
|
std::vector<std::wstring> DialogInstall::Tokenize(const std::wstring& str, const std::wstring& delimiters)
|
|
{
|
|
// Modified from http://www.digitalpeer.com/id/simple
|
|
std::vector<std::wstring> tokens;
|
|
std::wstring::size_type lastPos = str.find_first_not_of(delimiters, 0); // Skip delimiters at beginning
|
|
std::wstring::size_type pos = str.find_first_of(delimiters, lastPos); // Find first "non-delimiter"
|
|
|
|
while (std::wstring::npos != pos || std::wstring::npos != lastPos)
|
|
{
|
|
std::wstring tmpStr = str.substr(lastPos, pos - lastPos);
|
|
std::wstring::size_type tmpPos = tmpStr.find_first_not_of(L" \t");
|
|
if (tmpPos != std::wstring::npos)
|
|
{
|
|
tmpStr.erase(0, tmpPos);
|
|
tmpPos = tmpStr.find_last_not_of(L" \t");
|
|
if (tmpPos != std::wstring::npos)
|
|
{
|
|
tmpStr.resize(tmpPos + 1);
|
|
}
|
|
tokens.push_back(tmpStr);
|
|
}
|
|
else
|
|
{
|
|
tokens.push_back(L""); // Add empty string
|
|
}
|
|
lastPos = str.find_first_not_of(delimiters, pos); // Skip delimiters. Note the "not_of"
|
|
pos = str.find_first_of(delimiters, lastPos); // Find next "non-delimiter"
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/*
|
|
** Compares two version strings. Returns 0 if equal, 1 if A > B and -1 if A < B.
|
|
*/
|
|
int DialogInstall::CompareVersions(const std::wstring& strA, const std::wstring& strB)
|
|
{
|
|
if (strA.empty() && strB.empty()) return 0;
|
|
if (strA.empty()) return -1;
|
|
if (strB.empty()) return 1;
|
|
|
|
std::vector<std::wstring> arrayA = Tokenize(strA, L".");
|
|
std::vector<std::wstring> arrayB = Tokenize(strB, L".");
|
|
|
|
size_t len = max(arrayA.size(), arrayB.size());
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
int a = 0;
|
|
int b = 0;
|
|
|
|
if (i < arrayA.size())
|
|
{
|
|
a = _wtoi(arrayA[i].c_str());
|
|
}
|
|
if (i < arrayB.size())
|
|
{
|
|
b = _wtoi(arrayB[i].c_str());
|
|
}
|
|
|
|
if (a > b) return 1;
|
|
if (a < b) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool DialogInstall::CreateDirectoryRecursive(const std::wstring& path)
|
|
{
|
|
// Dirty...
|
|
std::wstring& directory = (std::wstring&)path;
|
|
const WCHAR* directorySz = directory.c_str();
|
|
|
|
bool failed = true;
|
|
std::wstring::size_type pos = std::wstring::npos;
|
|
while ((pos = failed ? directory.find_last_of(L'\\', pos) : directory.find_first_of(L'\\', pos)) != std::wstring::npos)
|
|
{
|
|
// Temporarily terminate string
|
|
directory[pos] = L'\0';
|
|
|
|
failed = CreateDirectory(directorySz, nullptr) == 0 && GetLastError() == ERROR_PATH_NOT_FOUND;
|
|
|
|
// Restore slash
|
|
directory[pos] = L'\\';
|
|
|
|
pos += failed ? -1 : 1;
|
|
}
|
|
|
|
return !failed;
|
|
}
|
|
|
|
std::wstring DialogInstall::GetFileVersionString(const WCHAR* fileName)
|
|
{
|
|
DWORD bufSize = GetFileVersionInfoSize(fileName, 0);
|
|
void* versionInfo = new WCHAR[bufSize];
|
|
void* fileVersion = 0;
|
|
UINT valueSize;
|
|
std::wstring result;
|
|
|
|
if (GetFileVersionInfo(fileName, 0, bufSize, versionInfo))
|
|
{
|
|
struct LANGANDCODEPAGE
|
|
{
|
|
WORD wLanguage;
|
|
WORD wCodePage;
|
|
} *languageInfo;
|
|
|
|
VerQueryValue(versionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&languageInfo, &valueSize);
|
|
WCHAR blockName[64];
|
|
_snwprintf_s(blockName, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\FileVersion", languageInfo[0].wLanguage, languageInfo[0].wCodePage);
|
|
|
|
VerQueryValue(versionInfo, blockName, &fileVersion, &valueSize);
|
|
if (valueSize)
|
|
{
|
|
result = (WCHAR*)fileVersion;
|
|
}
|
|
}
|
|
|
|
delete [] (WCHAR*)versionInfo;
|
|
return result;
|
|
}
|
|
|
|
std::wstring DialogInstall::GetDotNetVersionString()
|
|
{
|
|
WCHAR buffer[255];
|
|
HKEY hKey;
|
|
LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP", 0L, KEY_READ, &hKey);
|
|
std::wstring currVer(L"v0"), prevVer;
|
|
int i = 0;
|
|
|
|
while (lRet == ERROR_SUCCESS)
|
|
{
|
|
lRet = RegEnumKey(hKey, i, buffer, 255);
|
|
if (buffer[0] == L'v')
|
|
{
|
|
currVer = buffer;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
RegCloseKey(hKey);
|
|
currVer.erase(0, 1); // Get rid of the 'v'
|
|
return currVer;
|
|
}
|
|
|
|
std::wstring DialogInstall::GetWindowsVersionString()
|
|
{
|
|
WCHAR buffer[16];
|
|
OSVERSIONINFOEX osvi = {sizeof(OSVERSIONINFOEX)};
|
|
GetVersionEx((OSVERSIONINFO*)&osvi);
|
|
_snwprintf_s(buffer, _TRUNCATE, L"%d.%d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
//
|
|
// Install tab
|
|
//
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
/*
|
|
** Constructor.
|
|
**
|
|
*/
|
|
DialogInstall::TabInstall::TabInstall(HWND wnd) : Tab(GetInstanceHandle(), wnd, IDD_INSTALL_TAB, DlgProc)
|
|
{
|
|
}
|
|
|
|
void DialogInstall::TabInstall::Initialize()
|
|
{
|
|
HWND item = GetDlgItem(m_Window, IDC_INSTALLTAB_COMPONENTS_LIST);
|
|
|
|
DWORD extendedFlags = LVS_EX_CHECKBOXES | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT;
|
|
|
|
if (GetOSPlatform() >= OSPLATFORM_VISTA)
|
|
{
|
|
extendedFlags |= LVS_EX_DOUBLEBUFFER;
|
|
SetWindowTheme(item, L"explorer", nullptr);
|
|
}
|
|
|
|
ListView_EnableGroupView(item, TRUE);
|
|
ListView_SetExtendedListViewStyleEx(item, 0, extendedFlags);
|
|
|
|
// Add columns
|
|
LVCOLUMN lvc;
|
|
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
|
|
lvc.fmt = LVCFMT_LEFT;
|
|
lvc.iSubItem = 0;
|
|
lvc.cx = 180;
|
|
lvc.pszText = L"Name";
|
|
ListView_InsertColumn(item, 0, &lvc);
|
|
lvc.iSubItem = 1;
|
|
lvc.cx = 150;
|
|
lvc.pszText = L"Action";
|
|
ListView_InsertColumn(item, 1, &lvc);
|
|
|
|
// Add groups and items
|
|
LVGROUP lvg;
|
|
lvg.cbSize = sizeof(LVGROUP);
|
|
lvg.mask = LVGF_HEADER | LVGF_GROUPID | LVGF_STATE;
|
|
lvg.state = (GetOSPlatform() >= OSPLATFORM_VISTA) ? LVGS_COLLAPSIBLE : LVGS_NORMAL;
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_TEXT | LVIF_GROUPID | LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
|
|
auto addComponent = [&](const WCHAR* name, const std::set<std::wstring>& items, const std::wstring& path, int groupId)
|
|
{
|
|
lvg.iGroupId = groupId;
|
|
lvg.pszHeader = (WCHAR*)name;
|
|
ListView_InsertGroup(item, groupId, &lvg);
|
|
|
|
lvi.iGroupId = groupId;
|
|
lvi.iItem = 0;
|
|
for (auto iter = items.cbegin(); iter != items.cend(); ++iter)
|
|
{
|
|
lvi.pszText = (WCHAR*)(*iter).c_str();
|
|
lvi.lParam = (LPARAM)&(*iter);
|
|
ListView_InsertItem(item, &lvi);
|
|
ListView_SetCheckState(item, lvi.iItem, TRUE);
|
|
|
|
std::wstring itemPath = path + *iter;
|
|
WCHAR* text = L"Add";
|
|
if (_waccess(itemPath.c_str(), 0) != -1)
|
|
{
|
|
bool backup = groupId == 0 && c_Dialog->m_BackupSkins && !c_Dialog->m_BackupPackage;
|
|
text = backup ? L"Backup and replace" : L"Replace";
|
|
}
|
|
ListView_SetItemText(item, lvi.iItem, 1, text);
|
|
|
|
++lvi.iItem;
|
|
}
|
|
};
|
|
|
|
addComponent(L"Skins", c_Dialog->m_PackageSkins, g_Data.skinsPath, 0);
|
|
addComponent(L"Layouts", c_Dialog->m_PackageLayouts, g_Data.settingsPath + L"Layouts\\", 1);
|
|
addComponent(L"Addons", c_Dialog->m_PackageAddons, g_Data.settingsPath + L"Addons\\", 2);
|
|
addComponent(L"Plugins", c_Dialog->m_PackagePlugins, g_Data.settingsPath + L"Plugins\\", 3);
|
|
|
|
item = GetDlgItem(m_Window, IDC_INSTALLTAB_THEME_CHECKBOX);
|
|
if (!c_Dialog->m_LoadLayout.empty())
|
|
{
|
|
Button_SetCheck(item, BST_CHECKED);
|
|
}
|
|
else if (!c_Dialog->m_LoadSkins.empty())
|
|
{
|
|
SetWindowText(item, L"Load included skins");
|
|
Button_SetCheck(item, BST_CHECKED);
|
|
}
|
|
else
|
|
{
|
|
ShowWindow(item, SW_HIDE);
|
|
}
|
|
|
|
m_Initialized = true;
|
|
}
|
|
|
|
INT_PTR CALLBACK DialogInstall::TabInstall::DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return FALSE;
|
|
}
|