diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c15303..9abac2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ add_executable(Rebuilder WIN32 res/res.rc src/app.cpp src/app.h + src/launcher.cpp + src/launcher.h src/window.cpp src/window.h ) @@ -19,3 +21,5 @@ if (BUILD_UNICODE) target_compile_definitions(Rebuilder PRIVATE UNICODE _UNICODE) target_link_options(Rebuilder PRIVATE /entry:wWinMainCRTStartup) endif() + +target_link_libraries(Rebuilder PRIVATE shlwapi.lib) diff --git a/src/launcher.cpp b/src/launcher.cpp new file mode 100644 index 0000000..a7a3462 --- /dev/null +++ b/src/launcher.cpp @@ -0,0 +1,115 @@ +#include "launcher.h" + +#include +#include + +HANDLE Launcher::Launch(HWND parent) +{ + HANDLE ret = NULL; + + // Find the installation + LPTSTR filename = FindInstallation(parent); + + if (filename) { + // If we found it, launch now + PROCESS_INFORMATION pi; + STARTUPINFO si; + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + + if (CreateProcess(NULL, filename, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { + ret = pi.hProcess; + + if (!Patch(ret) && MessageBox(parent, _T("One or more patches failed. Would you like to continue?"), NULL, MB_YESNO) == IDNO) { + // Something went wrong, so we'll terminate this process now + TerminateProcess(ret, 1); + ret = NULL; + } else { + // Otherwise resume + ResumeThread(pi.hThread); + } + } else { + TCHAR err[2048]; + _stprintf(err, _T("Failed to create process with error 0x%lx"), GetLastError()); + MessageBox(parent, err, NULL, 0); + } + } + + delete [] filename; + + return ret; +} + +LPTSTR Launcher::FindInstallation(HWND parent) +{ + // Search for LEGO Island disk path + DWORD value_sz; + + TCHAR *isle_diskpath = NULL; + + // On install, LEGO Island records its installation directory in the registry + HKEY hKey; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Mindscape\\LEGO Island"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { + LONG ret = RegQueryValueEx(hKey, _T("diskpath"), NULL, NULL, NULL, &value_sz); + if (ret == ERROR_SUCCESS) { + // Get value from registry + TCHAR *reg_val = new TCHAR[value_sz]; + RegQueryValueEx(hKey, _T("diskpath"), NULL, NULL, (BYTE*)reg_val, &value_sz); + + // Append ISLE.EXE to diskpath + isle_diskpath = new TCHAR[MAX_PATH]; + wsprintf(isle_diskpath, _T("%s\\ISLE.EXE"), reg_val); + + // Delete reg val + delete [] reg_val; + } + + RegCloseKey(hKey); + } + + // Validate diskpath, either we couldn't find the registry entry or it was incorrect somehow + while (!isle_diskpath || !PathFileExists(isle_diskpath)) { + if (!isle_diskpath) { + // Allocate diskpath if it hasn't been allocated yet + isle_diskpath = new TCHAR[MAX_PATH]; + } + + // Ask user where LEGO Island is installed + OPENFILENAME fn; + ZeroMemory(&fn, sizeof(fn)); + fn.hwndOwner = parent; + fn.lStructSize = sizeof(fn); + fn.lpstrFile = isle_diskpath; + fn.lpstrFile[0] = '\0'; + fn.nMaxFile = MAX_PATH; + fn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + fn.lpstrTitle = _T("Where is LEGO Island installed?"); + fn.lpstrFilter = _T("ISLE.EXE\0ISLE.EXE\0"); + + if (!GetOpenFileName(&fn)) { + // If they cancelled the dialog, break out of the loop + delete [] isle_diskpath; + isle_diskpath = NULL; + break; + } + } + + return isle_diskpath; +} + +BOOL Launcher::Patch(HANDLE process) +{ + UINT32 width = 320; + UINT32 height = 240; + + if (!WriteProcessMemory(process, (LPVOID)0x00410048, &width, sizeof(width), NULL)) { + return FALSE; + } + + if (!WriteProcessMemory(process, (LPVOID)0x0041004C, &height, sizeof(height), NULL)) { + return FALSE; + } + + return TRUE; +} diff --git a/src/launcher.h b/src/launcher.h new file mode 100644 index 0000000..82e40f3 --- /dev/null +++ b/src/launcher.h @@ -0,0 +1,20 @@ +#ifndef LAUNCHER_H +#define LAUNCHER_H + +#include + +class Launcher +{ +public: + Launcher(); + + static HANDLE Launch(HWND parent); + +private: + static LPTSTR FindInstallation(HWND parent); + + static BOOL Patch(HANDLE process); + +}; + +#endif // LAUNCHER_H diff --git a/src/window.cpp b/src/window.cpp index 3d933dc..ef8bdd9 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,6 +1,8 @@ #include "window.h" -#define super CFrameWnd +#include "launcher.h" + +#define super CWnd CRebuilderWindow::CRebuilderWindow() { @@ -8,45 +10,24 @@ CRebuilderWindow::CRebuilderWindow() static const UINT defaultWindowWidth = 420; static const UINT defaultWindowHeight = 420; + // Register custom window class + LPCTSTR wndclass = AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, + LoadCursor(NULL, IDC_ARROW), + (HBRUSH) (COLOR_WINDOW), + LoadIcon(AfxGetInstanceHandle(), _T("IDI_ICON1"))); + // Create form - Create(NULL, _T("LEGO Island Rebuilder")); - - // Set window icon to application icon - TCHAR filename[MAX_PATH]; - GetModuleFileName(NULL, filename, MAX_PATH); - WORD index; - HICON icon = ExtractAssociatedIcon(AfxGetInstanceHandle(), filename, &index); - SetIcon(icon, TRUE); - - // Get default Win32 dialog font - m_fDialogFont.CreateStockObject(DEFAULT_GUI_FONT); - - // Create bolded variant of the above - LOGFONT lf; - m_fDialogFont.GetLogFont(&lf); - lf.lfWeight = FW_BOLD; - m_fBoldDialogFont.CreateFontIndirect(&lf); - - // Get information about font height for layout - HDC hDC = ::GetDC(NULL); - HGDIOBJ hFontOld = SelectObject(hDC, m_fDialogFont.GetSafeHandle()); - TEXTMETRIC tm; - GetTextMetrics(hDC, &tm); - m_nFontHeight = tm.tmHeight + tm.tmExternalLeading; - SelectObject(hDC, hFontOld); - ::ReleaseDC(NULL, hDC); + CreateEx(WS_EX_OVERLAPPEDWINDOW, wndclass, _T("LEGO Island Rebuilder"), WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL); + ModifyStyleEx(WS_EX_CLIENTEDGE, 0, 0); // Create title m_cTopLevelTitle.Create(_T("LEGO Island Rebuilder"), WS_CHILD | WS_VISIBLE | SS_CENTER, CRect(), this); - m_cTopLevelTitle.SetFont(&m_fBoldDialogFont); // Create subtitle m_cTopLevelSubtitle.Create(_T("by MattKC (itsmattkc.com)"), WS_CHILD | WS_VISIBLE | SS_CENTER, CRect(), this); - m_cTopLevelSubtitle.SetFont(&m_fDialogFont); // Create tab control - m_cTabCtrl.Create(WS_CHILD | WS_VISIBLE, CRect(), this, IDI_TABCTRL); - m_cTabCtrl.SetFont(&m_fDialogFont); + m_cTabCtrl.Create(WS_CHILD | WS_VISIBLE, CRect(), this, ID_TABCTRL); // Add "patches" tab TCITEM patchesItem; @@ -63,46 +44,42 @@ CRebuilderWindow::CRebuilderWindow() m_cTabCtrl.InsertItem(1, &musicItem); // Create run button - m_cRunBtn.Create(_T("Run"), WS_CHILD | WS_VISIBLE, CRect(), this, IDI_RUN); - m_cRunBtn.SetFont(&m_fBoldDialogFont); + m_cRunBtn.Create(_T("Run"), WS_CHILD | WS_VISIBLE, CRect(), this, ID_RUN); + + // Create run button + m_cKillBtn.Create(_T("Kill"), WS_CHILD, CRect(), this, ID_KILL); // Call this after all UI objects are created because this will call LayoutObjects SetWindowPos(NULL, 0, 0, defaultWindowWidth, defaultWindowHeight, 0); CenterWindow(NULL); - // Set background color to Win32 window color - ::SetClassLong(GetSafeHwnd(), GCL_HBRBACKGROUND, COLOR_WINDOW); + // Set fonts + SetGUIFonts(); } -LRESULT CRebuilderWindow::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +void CRebuilderWindow::OnRunClick() { - switch (message) { - case WM_SIZE: - { - UINT width = LOWORD(lParam); - UINT height = HIWORD(lParam); + HANDLE proc = Launcher::Launch(this->GetSafeHwnd()); - LayoutObjects(width, height); - break; + if (proc) { + m_lProcesses.push_back(proc); + m_cRunBtn.ShowWindow(SW_HIDE); + m_cKillBtn.ShowWindow(SW_SHOWNORMAL); } - case WM_GETMINMAXINFO: - { - static const LONG minimumWindowWidth = 160; - static const LONG minimumWindowHeight = 160; - - MINMAXINFO *minmaxInfo = (MINMAXINFO*)lParam; - - minmaxInfo->ptMinTrackSize.x = minimumWindowWidth; - minmaxInfo->ptMinTrackSize.y = minimumWindowHeight; - - return 0; - } - } - - return super::WindowProc(message, wParam, lParam); } -void CRebuilderWindow::LayoutObjects(UINT width, UINT height) +void CRebuilderWindow::OnKillClick() +{ + for (std::vector::iterator it=m_lProcesses.begin(); it!=m_lProcesses.end(); it++) { + TerminateProcess(*it, 0); + } + m_lProcesses.clear(); + + m_cKillBtn.ShowWindow(SW_HIDE); + m_cRunBtn.ShowWindow(SW_SHOWNORMAL); +} + +void CRebuilderWindow::OnSize(UINT type, int width, int height) { const int padding = m_nFontHeight/2; const int dblPadding = padding * 2; @@ -119,8 +96,62 @@ void CRebuilderWindow::LayoutObjects(UINT width, UINT height) int bottomComponentStart = height - btnHeight - padding; int bottomComponentWidth = width - dblPadding; m_cRunBtn.SetWindowPos(NULL, padding, bottomComponentStart, bottomComponentWidth, btnHeight, 0); + m_cKillBtn.SetWindowPos(NULL, padding, bottomComponentStart, bottomComponentWidth, btnHeight, 0); // Center components int centerHeight = bottomComponentStart - topComponentEnd; m_cTabCtrl.SetWindowPos(NULL, padding, topComponentEnd + padding, bottomComponentWidth, centerHeight - dblPadding, 0); } + +void CRebuilderWindow::OnGetMinMaxInfo(MINMAXINFO *info) +{ + static const LONG minimumWindowWidth = 160; + static const LONG minimumWindowHeight = 160; + + info->ptMinTrackSize.x = minimumWindowWidth; + info->ptMinTrackSize.y = minimumWindowHeight; +} + +BOOL CRebuilderWindow::SetFont(HWND child, LPARAM font) +{ + ::SendMessage(child, WM_SETFONT, font, true); + return true; +} + +void CRebuilderWindow::SetGUIFonts() +{ + // Retrieve default GUI font + HFONT defaultFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + + // Set on all UI objects + EnumChildWindows(this->GetSafeHwnd(), (WNDENUMPROC)SetFont, (LPARAM)defaultFont); + + // Get LOGFONT to create bold variant + LOGFONT lf; + GetObject(defaultFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + + // Create font from LOGFONT + HFONT bold = CreateFontIndirect(&lf); + + // Set bold variant on relevant objects + SetFont(m_cTopLevelTitle.GetSafeHwnd(), (LPARAM)bold); + SetFont(m_cRunBtn.GetSafeHwnd(), (LPARAM)bold); + SetFont(m_cKillBtn.GetSafeHwnd(), (LPARAM)bold); + + // While here, get height of font for layout purposes + HDC hDC = ::GetDC(NULL); + HGDIOBJ hFontOld = SelectObject(hDC, defaultFont); + TEXTMETRIC tm; + GetTextMetrics(hDC, &tm); + m_nFontHeight = tm.tmHeight + tm.tmExternalLeading; + SelectObject(hDC, hFontOld); + ::ReleaseDC(NULL, hDC); +} + +BEGIN_MESSAGE_MAP(CRebuilderWindow, super) + ON_WM_SIZE() + ON_WM_GETMINMAXINFO() + ON_BN_CLICKED(ID_RUN, OnRunClick) + ON_BN_CLICKED(ID_KILL, OnKillClick) +END_MESSAGE_MAP() diff --git a/src/window.h b/src/window.h index ecba4d9..455d2eb 100644 --- a/src/window.h +++ b/src/window.h @@ -3,24 +3,32 @@ #include #include +#include class CRebuilderWindow : public CFrameWnd { public: CRebuilderWindow(); - virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); + afx_msg void OnRunClick(); + + afx_msg void OnKillClick(); + + afx_msg void OnSize(UINT type, int width, int height); + + afx_msg void OnGetMinMaxInfo(MINMAXINFO *info); + + static BOOL CALLBACK SetFont(HWND child, LPARAM font); private: - void LayoutObjects(UINT width, UINT height); + void SetGUIFonts(); enum { - IDI_RUN, - IDI_TABCTRL + ID_RUN = 1000, + ID_KILL, + ID_TABCTRL }; - CFont m_fDialogFont; - CFont m_fBoldDialogFont; UINT m_nFontHeight; CStatic m_cTopLevelTitle; @@ -29,6 +37,11 @@ private: CTabCtrl m_cTabCtrl; CButton m_cRunBtn; + CButton m_cKillBtn; + + std::vector m_lProcesses; + + DECLARE_MESSAGE_MAP() };