diff --git a/CMakeLists.txt b/CMakeLists.txt index 9abac2d..6ef3bf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,23 +3,48 @@ cmake_minimum_required(VERSION 3.5) project(Rebuilder LANGUAGES CXX) set(CMAKE_MFC_FLAG 2) +add_compile_definitions(_AFXDLL) option(BUILD_UNICODE "Build with Unicode support" ON) +# +# Build our code injected DLL +# +add_library(Rebld SHARED + lib/dllmain.cpp + lib/hooks.cpp + lib/hooks.h + lib/mmpassthru.cpp + lib/util.cpp + lib/util.h + lib/worker.cpp + lib/worker.h +) +target_link_libraries(Rebld PRIVATE winmm.lib) + +# +# Build launcher/configuration executable +# add_executable(Rebuilder WIN32 res/res.rc + res/resource.h src/app.cpp src/app.h + src/clinkstatic.cpp + src/clinkstatic.h src/launcher.cpp src/launcher.h src/window.cpp src/window.h ) -target_compile_definitions(Rebuilder PRIVATE _AFXDLL) if (BUILD_UNICODE) target_compile_definitions(Rebuilder PRIVATE UNICODE _UNICODE) + target_compile_definitions(Rebld PRIVATE UNICODE _UNICODE) target_link_options(Rebuilder PRIVATE /entry:wWinMainCRTStartup) endif() target_link_libraries(Rebuilder PRIVATE shlwapi.lib) + +# Ensure DLL is compiled before resource is built into executable +set_source_files_properties(res/res.rc PROPERTIES OBJECT_DEPENDS Rebld) diff --git a/lib/dllmain.cpp b/lib/dllmain.cpp new file mode 100644 index 0000000..463d4c8 --- /dev/null +++ b/lib/dllmain.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "worker.h" + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + // Allocate console + AllocConsole(); + + // Direct stdin/stderr/stdout to console + _tfreopen(TEXT("CONIN$"), TEXT("r"), stdin); + _tfreopen(TEXT("CONOUT$"), TEXT("w"), stderr); + _tfreopen(TEXT("CONOUT$"), TEXT("w"), stdout); + + // Print success line + printf("Injected successfully\n"); + + Patch(); + break; + } + + return TRUE; +} diff --git a/lib/hooks.cpp b/lib/hooks.cpp new file mode 100644 index 0000000..df6db6a --- /dev/null +++ b/lib/hooks.cpp @@ -0,0 +1,115 @@ +#include "hooks.h" + +#include + +#include "util.h" + +void InterceptOutputDebugStringA(LPCSTR s) +{ + printf("%s\n", s); + MessageBoxA(0,s,"LEGO Island sez",0); +} + +HWND WINAPI InterceptCreateWindowExA(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + HWND window = CreateWindowExA(dwExStyle, lpClassName, "LEGO Island: Rebuilt", dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + return window; +} + +HWND WINAPI InterceptFindWindowA(LPCSTR lpClassName, LPCSTR lpWindowName) +{ + return NULL; +} + +void ForceDDPixelFormatTo16(LPDDPIXELFORMAT lpDDPixelFormat) +{ + if (lpDDPixelFormat->dwRGBBitCount == 32) { + lpDDPixelFormat->dwRGBBitCount = 16; + lpDDPixelFormat->dwRBitMask = 0xF800; + lpDDPixelFormat->dwGBitMask = 0x07E0; + lpDDPixelFormat->dwBBitMask = 0x001F; + } +} + +void ForceDDSurfaceDescTo16(LPDDSURFACEDESC lpDDSurfaceDesc) +{ + DDPIXELFORMAT &pixfmt = lpDDSurfaceDesc->ddpfPixelFormat; + if (pixfmt.dwRGBBitCount == 32) { + // LEGO Island has no support for 32-bit surfaces, so we tell it we're on a 16-bit surface + lpDDSurfaceDesc->lPitch /= 2; + ForceDDPixelFormatTo16(&pixfmt); + } +} + +typedef HRESULT (WINAPI *ddSurfaceGetDescFunction)(LPDIRECTDRAWSURFACE lpDDSurface, LPDDSURFACEDESC lpDDSurfaceDesc); +ddSurfaceGetDescFunction originalDDSurfaceGetDescFunction = NULL; +HRESULT WINAPI InterceptSurfaceGetDesc(LPDIRECTDRAWSURFACE lpDDSurface, LPDDSURFACEDESC lpDDSurfaceDesc) +{ + HRESULT res = originalDDSurfaceGetDescFunction(lpDDSurface, lpDDSurfaceDesc); + + if (res == DD_OK) { + ForceDDSurfaceDescTo16(lpDDSurfaceDesc); + } + + return res; +} + +typedef HRESULT (WINAPI *ddSurfaceGetPixelFormatFunction)(LPDIRECTDRAWSURFACE lpDDSurface, LPDDPIXELFORMAT lpDDPixelFormat); +ddSurfaceGetPixelFormatFunction originalDDSurfaceGetPixelFunction = NULL; +HRESULT WINAPI InterceptSurfaceGetPixelFormatFunction(LPDIRECTDRAWSURFACE lpDDSurface, LPDDPIXELFORMAT lpDDPixelFormat) +{ + HRESULT res = originalDDSurfaceGetPixelFunction(lpDDSurface, lpDDPixelFormat); + + if (res == DD_OK) { + //ForceDDPixelFormatTo16(lpDDPixelFormat); + } + + return res; +} + +typedef HRESULT (WINAPI *ddCreateSurfaceFunction)(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *unknown); +ddCreateSurfaceFunction originalCreateSurfaceFunction = NULL; +HRESULT WINAPI InterceptCreateSurface(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *unknown) +{ + //ForceDDSurfaceDescTo16(lpDDSurfaceDesc); + + HRESULT res = originalCreateSurfaceFunction(lpDD, lpDDSurfaceDesc, lplpDDSurface, unknown); + + if (res == DD_OK) { + if (!originalDDSurfaceGetPixelFunction) { + originalDDSurfaceGetPixelFunction = (ddSurfaceGetPixelFormatFunction)OverwriteVirtualTable(*lplpDDSurface, 0x15, (LPVOID)InterceptSurfaceGetPixelFormatFunction); + } + + if (!originalDDSurfaceGetDescFunction) { + //originalDDSurfaceGetDescFunction = (ddSurfaceGetDescFunction)OverwriteVirtualTable(*lplpDDSurface, 0x16, (LPVOID)InterceptSurfaceGetDesc); + originalDDSurfaceGetDescFunction = (ddSurfaceGetDescFunction)OverwriteVirtualTable(*lplpDDSurface, 0x16, NULL); + } + } + + return res; +} + +typedef HRESULT (WINAPI *ddGetDisplayModeFunction)(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc); +ddGetDisplayModeFunction originalGetDisplayMode = NULL; +HRESULT WINAPI InterceptGetDisplayMode(LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) +{ + HRESULT res = originalGetDisplayMode(lpDD, lpDDSurfaceDesc); + + //ForceDDSurfaceDescTo16(lpDDSurfaceDesc); + + return res; +} + +ddCreateFunction ddCreateOriginal = NULL; +HRESULT WINAPI InterceptDirectDrawCreate(GUID *lpGUID, LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuter) +{ + HRESULT res = ddCreateOriginal(lpGUID, lplpDD, pUnkOuter); + + if (res == DD_OK && !originalGetDisplayMode) { + originalGetDisplayMode = (ddGetDisplayModeFunction)OverwriteVirtualTable(*lplpDD, 0xC, (LPVOID)InterceptGetDisplayMode); + originalCreateSurfaceFunction = (ddCreateSurfaceFunction)OverwriteVirtualTable(*lplpDD, 0x6, (LPVOID)InterceptCreateSurface); + } + + return res; +} diff --git a/lib/hooks.h b/lib/hooks.h new file mode 100644 index 0000000..dd714e9 --- /dev/null +++ b/lib/hooks.h @@ -0,0 +1,32 @@ +#ifndef HOOKS_H +#define HOOKS_H + +#include +#include + +void InterceptOutputDebugStringA(LPCSTR s); + +HWND WINAPI InterceptCreateWindowExA( + DWORD dwExStyle, + LPCSTR lpClassName, + LPCSTR lpWindowName, + DWORD dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HMENU hMenu, + HINSTANCE hInstance, + LPVOID lpParam +); + +HWND WINAPI InterceptFindWindowA(LPCSTR lpClassName, LPCSTR lpWindowName); + +typedef HRESULT (WINAPI *ddCreateFunction)(GUID *lpGUID, LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuterS); +extern ddCreateFunction ddCreateOriginal; +HRESULT WINAPI InterceptDirectDrawCreate(GUID *lpGUID, LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuter); + +HRESULT WINAPI InterceptSurfaceGetDesc(LPDIRECTDRAWSURFACE lpDDSurface, LPDDSURFACEDESC lpDDSurfaceDesc); + +#endif // HOOKS_H diff --git a/lib/mmpassthru.cpp b/lib/mmpassthru.cpp new file mode 100644 index 0000000..533db7f --- /dev/null +++ b/lib/mmpassthru.cpp @@ -0,0 +1,6 @@ +#include + +extern "C" __declspec(dllexport) DWORD rbldGetTime() +{ + return timeGetTime(); +} diff --git a/lib/util.cpp b/lib/util.cpp new file mode 100644 index 0000000..a616019 --- /dev/null +++ b/lib/util.cpp @@ -0,0 +1,139 @@ +#include "util.h" + +#include + +BOOL WriteMemory(LPVOID destination, LPVOID source, size_t length, LPVOID oldData) +{ + DWORD oldProtec; + + if (VirtualProtect(destination, length, PAGE_EXECUTE_READWRITE, &oldProtec)) { + // Read data out if requested + if (oldData) { + memcpy(oldData, destination, length); + } + + // Write data if provided + if (source) { + memcpy(destination, source, length); + } + + // Restore original protection + VirtualProtect(destination, length, oldProtec, &oldProtec); + + return TRUE; + } else { + return FALSE; + } +} + +SIZE_T SearchReplacePattern(LPVOID imageBase, LPCVOID search, LPCVOID replace, SIZE_T count, BOOL only_once) +{ + SIZE_T instances = 0; + + HANDLE process = GetCurrentProcess(); + + MEMORY_BASIC_INFORMATION mbi = {0}; + + // Loop through memory pages + UINT_PTR addr = (UINT_PTR)imageBase; + while ((!instances || !only_once) && VirtualQueryEx(process, (LPVOID)addr, &mbi, sizeof(mbi)) && mbi.AllocationBase == imageBase) { + if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS) { + DWORD oldProtec; + + // Try to gain access to this memory page + if (VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &oldProtec)) { + // Loop through every byte to find the pattern + SIZE_T maxOffset = mbi.RegionSize - count; + for (SIZE_T i=0; ie_lfanew); + + // Retrieve imports directory + IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + + // Retrieve import descriptor + PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((UINT_PTR)imageBase + importsDirectory.VirtualAddress); + + while (importDescriptor->Name != NULL) { + LPCSTR libraryName = (LPCSTR)importDescriptor->Name + (UINT_PTR)imageBase; + HMODULE library = LoadLibraryA(libraryName); + + if (library) { + PIMAGE_THUNK_DATA originalFirstThunk = (PIMAGE_THUNK_DATA)((UINT_PTR)imageBase + importDescriptor->OriginalFirstThunk); + PIMAGE_THUNK_DATA firstThunk = (PIMAGE_THUNK_DATA)((UINT_PTR)imageBase + importDescriptor->FirstThunk); + + while (originalFirstThunk->u1.AddressOfData != NULL) { + PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((UINT_PTR)imageBase + (UINT_PTR)originalFirstThunk->u1.AddressOfData); + + if (!strcmp((const char*)functionName->Name, overrideFunction)) { + LPVOID originalFunction = firstThunk->u1.Function; + firstThunk->u1.Function = (PDWORD)override; + + // Return original function and end loop here + printf("Hooked %s\n", overrideFunction); + return originalFunction; + } + ++originalFirstThunk; + ++firstThunk; + } + } + + importDescriptor++; + } + + return NULL; +} + +LPVOID OverwriteVirtualTable(LPVOID object, SIZE_T methodIndex, LPVOID overrideFunction) +{ + LPVOID *vtable = ((LPVOID**)(object))[0]; + LPVOID &functionAddress = vtable[methodIndex]; + LPVOID originalFunction = functionAddress; + + if (overrideFunction) { + functionAddress = overrideFunction; + printf("Hooked vtable %p+%lu\n", vtable, methodIndex); + } + + return originalFunction; +} + +BOOL OverwriteCall(LPVOID destination, LPVOID localCall) +{ + char callInst[5]; + + callInst[0] = '\xE8'; + + *(DWORD*)(&callInst[1]) = (DWORD)localCall - ((DWORD)destination + 5); + + return WriteMemory(destination, callInst, 5); +} diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..cd6ca6d --- /dev/null +++ b/lib/util.h @@ -0,0 +1,16 @@ +#ifndef UTIL_H +#define UTIL_H + +#include + +BOOL WriteMemory(LPVOID destination, LPVOID source, size_t length, LPVOID oldData = NULL); + +BOOL OverwriteCall(LPVOID destination, LPVOID localCall); + +SIZE_T SearchReplacePattern(LPVOID imageBase, LPCVOID search, LPCVOID replace, SIZE_T count, BOOL only_once = FALSE); + +LPVOID OverwriteImport(LPVOID imageBase, LPCSTR overrideFunction, LPVOID override); + +LPVOID OverwriteVirtualTable(LPVOID object, SIZE_T methodIndex, LPVOID overrideFunction); + +#endif // UTIL_H diff --git a/lib/worker.cpp b/lib/worker.cpp new file mode 100644 index 0000000..2e8d52e --- /dev/null +++ b/lib/worker.cpp @@ -0,0 +1,45 @@ +#include "worker.h" + +#include + +#include "hooks.h" +#include "util.h" + +DWORD WINAPI Patch() +{ + MessageBoxA(0,"Connect debugger now",0,0); + + // Hook import address table + LPVOID exeBase = GetModuleHandle(TEXT("ISLE.EXE")); + LPVOID dllBase = GetModuleHandle(TEXT("LEGO1.DLL")); + + // Redirect various imports + OverwriteImport(exeBase, "CreateWindowExA", (LPVOID)InterceptCreateWindowExA); + OverwriteImport(exeBase, "FindWindowA", (LPVOID)InterceptFindWindowA); + OverwriteImport(dllBase, "OutputDebugStringA", (LPVOID)InterceptOutputDebugStringA); + ddCreateOriginal = (ddCreateFunction)OverwriteImport(dllBase, "DirectDrawCreate", (LPVOID)InterceptDirectDrawCreate); + + // Stay active when defocused + SearchReplacePattern(exeBase, "\x89\x58\x70", "\x90\x90\x90", 3); + SearchReplacePattern(dllBase, "\xC7\x44\x24\x24\xE0\x00\x00\x00", "\xC7\x44\x24\x24\xE0\x80\x00\x00", 8); + SearchReplacePattern(dllBase, "\xC7\x44\x24\x24\xB0\x00\x00\x00", "\xC7\x44\x24\x24\xB0\x80\x00\x00", 8); + SearchReplacePattern(dllBase, "\xC7\x45\xCC\x11\x00\x00\x00", "\xC7\x45\xCC\x11\x80\x00\x00", 7); + SearchReplacePattern(dllBase, "\xC7\x45\xCC\xE0\x00\x00\x00", "\xC7\x45\xCC\xE0\x80\x00\x00", 7); + + // DDRAW GetSurfaceDesc Override + //OverwriteCall((LPVOID) ((UINT_PTR)dllBase+0xBA7D5), (LPVOID)InterceptSurfaceGetDesc); + + // Window size hack + /*SearchReplacePattern(exeBase, + "\x80\x02\x00\x00\xE0\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x80\x02\x00\x00\xE0\x01\x00\x00", + "\x40\x01\x00\x00\xE0\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x80\x02\x00\x00\xE0\x01\x00\x00", 24);*/ + + /*char debug[5]; + debug[0] = 0xE8; + *(UINT_PTR*)(&debug[1]) = (UINT_PTR)InterceptOutputDebugStringA; + if (SIZE_T replacements = SearchReplacePattern(legoBase, "\xE8\x34\x08\x00\x00", debug, 5)) { + printf("Hooked some other debug function %lu times", replacements); + }*/ + + return 0; +} diff --git a/lib/worker.h b/lib/worker.h new file mode 100644 index 0000000..fba5d55 --- /dev/null +++ b/lib/worker.h @@ -0,0 +1,8 @@ +#ifndef WORKER_H +#define WORKER_H + +#include + +DWORD WINAPI Patch(); + +#endif // WORKER_H diff --git a/res/res.manifest b/res/res.manifest index 7de501f..71f23b6 100644 --- a/res/res.manifest +++ b/res/res.manifest @@ -1,5 +1,5 @@ - + LEGO Island modding tool and launcher. @@ -7,4 +7,17 @@ + + + true + + + + + + + + + + diff --git a/res/res.rc b/res/res.rc index a83d23f..9171877 100644 --- a/res/res.rc +++ b/res/res.rc @@ -1,2 +1,21 @@ +#include "resource.h" + +// Application icon IDI_ICON1 ICON DISCARDABLE "mama.ico" -1 24 "res.manifest" + +// Manifest to enable visual styles +1 RC_MANIFEST "res.manifest" + +// String table +STRINGTABLE +BEGIN + IDS_TITLE "LEGO Island Rebuilder" + IDS_SUBTITLE "by MattKC (itsmattkc.com)" + IDS_PATCHES "Patches" + IDS_MUSIC "Music" + IDS_RUN "Run" + IDS_KILL "Kill" +END + +// Include worker DLL in executable for better portability +WORKER_DLL RCDATA "rebld.dll" diff --git a/res/resource.h b/res/resource.h new file mode 100644 index 0000000..12abcd2 --- /dev/null +++ b/res/resource.h @@ -0,0 +1,15 @@ +#ifndef RESOURCE_H +#define RESOURCE_H + +#define IDS_TITLE 0 +#define IDS_SUBTITLE 1 +#define IDS_PATCHES 2 +#define IDS_MUSIC 3 +#define IDS_RUN 4 +#define IDS_KILL 5 + +#define RC_MANIFEST 24 + +#define WORKER_DLL 300 + +#endif // RESOURCE_H diff --git a/src/clinkstatic.cpp b/src/clinkstatic.cpp new file mode 100644 index 0000000..fddc8c2 --- /dev/null +++ b/src/clinkstatic.cpp @@ -0,0 +1,55 @@ +#include "clinkstatic.h" + +#define super CStatic + +#ifndef IDC_HAND +#define IDC_HAND MAKEINTRESOURCE(32649) +#endif + +CLinkStatic::CLinkStatic() +{ + // We want a pointing hand cursor and different Windows versions provide this differently + OSVERSIONINFO info; + ZeroMemory(&info, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + GetVersionEx(&info); + + if (info.dwMajorVersion <= 4) { + // Windows 9x didn't have a pointing hand cursor so we'll pull it from winhlp32 + CString strWndDir; + GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH); + strWndDir.ReleaseBuffer(); + + strWndDir += _T("\\winhlp32.exe"); + HMODULE hModule = LoadLibrary(strWndDir); + if (hModule) { + HCURSOR hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106)); + if (hHandCursor) + m_hPointHand = CopyCursor(hHandCursor); + } + FreeLibrary(hModule); + } else { + // With NT5+, we can use IDC_HAND + m_hPointHand = LoadCursor(NULL, IDC_HAND); + } +} + +BOOL CLinkStatic::OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message) +{ + + ::SetCursor(m_hPointHand); + return TRUE; +} + +HBRUSH CLinkStatic::CtlColor(CDC *pDC, UINT nCtlColor) +{ + pDC->SetTextColor(RGB(0, 0, 240)); + pDC->SetBkMode(TRANSPARENT); + + return (HBRUSH) GetStockObject(NULL_BRUSH); +} + +BEGIN_MESSAGE_MAP(CLinkStatic, super) + ON_WM_SETCURSOR() + ON_WM_CTLCOLOR_REFLECT() +END_MESSAGE_MAP() diff --git a/src/clinkstatic.h b/src/clinkstatic.h new file mode 100644 index 0000000..a942ffe --- /dev/null +++ b/src/clinkstatic.h @@ -0,0 +1,22 @@ +#ifndef CLINKSTATIC_H +#define CLINKSTATIC_H + +#include + +class CLinkStatic : public CStatic +{ +public: + CLinkStatic(); + + afx_msg BOOL OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message); + + afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor); + +private: + HCURSOR m_hPointHand; + + DECLARE_MESSAGE_MAP() + +}; + +#endif // CLINKSTATIC_H diff --git a/src/launcher.cpp b/src/launcher.cpp index a7a3462..ff65787 100644 --- a/src/launcher.cpp +++ b/src/launcher.cpp @@ -3,50 +3,68 @@ #include #include +#include "../res/resource.h" + 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); - } + TCHAR filename[MAX_PATH]; + if (!FindInstallation(parent, filename)) { + // Don't show error message because presumably the user cancelled out of looking for ISLE + return NULL; } - delete [] filename; + // If we found it, make a copy to temp + TCHAR copiedFile[MAX_PATH]; + TCHAR libraryFile[MAX_PATH]; + if (!CopyIsleToTemp(filename, copiedFile)) { + MessageBox(parent, _T("Failed to copy to temp"), NULL, 0); + return NULL; + } - return ret; + // Extract REBLD.DLL which contains our patches + if (!ExtractLibrary(libraryFile, MAX_PATH)) { + MessageBox(parent, _T("Failed to extract to temp"), NULL, 0); + return NULL; + } + + // Patch our copied ISLE to import our DLL + if (!PatchIsle(copiedFile)) { + MessageBox(parent, _T("Failed to patch import"), NULL, 0); + return NULL; + } + + // Get ISLE directory only + TCHAR srcDir[MAX_PATH]; + _tcscpy(srcDir, filename); + PathRemoveFileSpec(srcDir); + + // Start launching our copy + PROCESS_INFORMATION pi; + STARTUPINFO si; + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + + if (!CreateProcess(NULL, copiedFile, NULL, NULL, FALSE, 0, NULL, srcDir, &si, &pi)) { + TCHAR err[2048]; + _stprintf(err, _T("Failed to create process with error 0x%lx"), GetLastError()); + MessageBox(parent, err, NULL, 0); + return NULL; + } + + return pi.hProcess; } -LPTSTR Launcher::FindInstallation(HWND parent) +BOOL Launcher::FindInstallation(HWND parent, LPTSTR str) { // Search for LEGO Island disk path DWORD value_sz; - TCHAR *isle_diskpath = NULL; + BOOL success = TRUE; + + // Start with empty string + str[0] = 0; // On install, LEGO Island records its installation directory in the registry HKEY hKey; @@ -54,33 +72,23 @@ LPTSTR Launcher::FindInstallation(HWND parent) 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); + RegQueryValueEx(hKey, _T("diskpath"), NULL, NULL, (BYTE*)str, &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; + _tcscat(str, _T("\\ISLE.EXE")); } 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]; - } - + while (!_tcslen(str) || !PathFileExists(str)) { // 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 = str; fn.lpstrFile[0] = '\0'; fn.nMaxFile = MAX_PATH; fn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; @@ -89,27 +97,119 @@ LPTSTR Launcher::FindInstallation(HWND parent) if (!GetOpenFileName(&fn)) { // If they cancelled the dialog, break out of the loop - delete [] isle_diskpath; - isle_diskpath = NULL; + success = FALSE; break; } } - return isle_diskpath; + return success; } -BOOL Launcher::Patch(HANDLE process) +BOOL Launcher::ExtractLibrary(LPTSTR str, SIZE_T len) { - UINT32 width = 320; - UINT32 height = 240; - - if (!WriteProcessMemory(process, (LPVOID)0x00410048, &width, sizeof(width), NULL)) { + // Find temporary location to store DLL + if (!GetTempPath(len, str)) { return FALSE; } - if (!WriteProcessMemory(process, (LPVOID)0x0041004C, &height, sizeof(height), NULL)) { + _tcscat(str, _T("REBLD.DLL")); + + // Open file + HANDLE file = CreateFile(str, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (!file) { return FALSE; } - return TRUE; + // Extract DLL which should have been compiled in as a resource + HRSRC res = FindResource(AfxGetInstanceHandle(), MAKEINTRESOURCE(WORKER_DLL), RT_RCDATA); + if (!res) { + return FALSE; + } + + HGLOBAL res_resource = LoadResource(NULL, res); + if (!res_resource) { + return FALSE; + } + + LPVOID res_data = LockResource(res_resource); + DWORD res_sz = SizeofResource(NULL, res); + + DWORD writtenBytes; + BOOL success = WriteFile(file, res_data, res_sz, &writtenBytes, NULL); + + UnlockResource(res_resource); + + FreeResource(res_resource); + + CloseHandle(file); + + return success; +} + +BOOL Launcher::CopyIsleToTemp(LPCTSTR src, LPTSTR dst) +{ + BOOL success = FALSE; + + TCHAR tempDir[MAX_PATH]; + if (GetTempPath(MAX_PATH, tempDir)) { + _tcscpy(dst, tempDir); + _tcscat(dst, _T("ISLE.EXE")); + if (CopyFile(src, dst, FALSE)) { + // Force our copy to load our DLL + success = TRUE; + } + } + + return success; +} + +BOOL Launcher::PatchIsle(LPCTSTR filename) +{ + BOOL success = false; + + if (HANDLE file = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0)) { + // Force ISLE to load our DLL. This system will work on all platforms including Windows 95. + if (ReplacePatternInFile(file, "timeGetTime\0WINMM", "rbldGetTime\0REBLD", 17)) { + success = true; + } + + CloseHandle(file); + } + + return success; +} + +BOOL Launcher::ReplacePatternInFile(HANDLE file, const char *pattern, const char *replace, LONG sz) +{ + BOOL success = FALSE; + + SetFilePointer(file, 0, 0, FILE_BEGIN); + + char *data = new char[sz]; + + // Find pattern in the file + DWORD nbBytesRead; + do { + ReadFile(file, data, sz, &nbBytesRead, NULL); + SetFilePointer(file, -sz + 1, 0, FILE_CURRENT); + } while ((LONG)nbBytesRead == sz && memcmp(data, pattern, sz)); + + if ((LONG)nbBytesRead == sz) { + // Must have found pattern + SetFilePointer(file, -1, 0, FILE_CURRENT); + + // Overwrite with replace + DWORD nbBytesWritten; // We don't use this value, but Windows 95 will fail without it + if (WriteFile(file, replace, sz, &nbBytesWritten, NULL)) { + success = TRUE; + } else { + char buf[200]; + sprintf(buf, "Failed to write to file with error: %lx", GetLastError()); + MessageBoxA(0, buf, 0, 0); + } + } + + delete [] data; + + return success; } diff --git a/src/launcher.h b/src/launcher.h index 82e40f3..4d54718 100644 --- a/src/launcher.h +++ b/src/launcher.h @@ -11,9 +11,15 @@ public: static HANDLE Launch(HWND parent); private: - static LPTSTR FindInstallation(HWND parent); + static BOOL FindInstallation(HWND parent, LPTSTR isle_diskpath); - static BOOL Patch(HANDLE process); + static BOOL ExtractLibrary(LPTSTR str, SIZE_T len); + + static BOOL CopyIsleToTemp(LPCTSTR src, LPTSTR dst); + + static BOOL PatchIsle(LPCTSTR filename); + + static BOOL ReplacePatternInFile(HANDLE file, const char *pattern, const char *replace, LONG sz); }; diff --git a/src/window.cpp b/src/window.cpp index ef8bdd9..4464f04 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,60 +1,84 @@ #include "window.h" +#include + #include "launcher.h" +#include "../res/resource.h" #define super CWnd +static const int WM_CHILD_CLOSED = WM_USER + 1; + +CString GetResourceString(UINT id) +{ + CString cstr; + cstr.LoadString(id); + return cstr; +} + CRebuilderWindow::CRebuilderWindow() { - // Declare default width/height - 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), + (HBRUSH) COLOR_WINDOW, LoadIcon(AfxGetInstanceHandle(), _T("IDI_ICON1"))); // Create form - CreateEx(WS_EX_OVERLAPPEDWINDOW, wndclass, _T("LEGO Island Rebuilder"), WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL); + CreateEx(WS_EX_OVERLAPPEDWINDOW, wndclass, GetResourceString(IDS_TITLE), 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.Create(GetResourceString(IDS_TITLE), WS_CHILD | WS_VISIBLE | SS_CENTER, CRect(), this); // Create subtitle - m_cTopLevelSubtitle.Create(_T("by MattKC (itsmattkc.com)"), WS_CHILD | WS_VISIBLE | SS_CENTER, CRect(), this); + m_cTopLevelSubtitle.Create(GetResourceString(IDS_SUBTITLE), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_NOTIFY, CRect(), this, ID_SUBTITLE); // Create tab control m_cTabCtrl.Create(WS_CHILD | WS_VISIBLE, CRect(), this, ID_TABCTRL); + // Initialize common TCITEM + TCITEM tabItem; + ZeroMemory(&tabItem, sizeof(tabItem)); + tabItem.mask |= TCIF_TEXT; + tabItem.pszText = new TCHAR[100]; + // Add "patches" tab - TCITEM patchesItem; - ZeroMemory(&patchesItem, sizeof(patchesItem)); - patchesItem.pszText = _T("Patches"); - patchesItem.mask |= TCIF_TEXT; - m_cTabCtrl.InsertItem(0, &patchesItem); + _tcscpy(tabItem.pszText, GetResourceString(IDS_PATCHES)); + m_cTabCtrl.InsertItem(TAB_PATCHES, &tabItem); // Add "music" tab - TCITEM musicItem; - ZeroMemory(&musicItem, sizeof(musicItem)); - musicItem.pszText = _T("Music"); - musicItem.mask |= TCIF_TEXT; - m_cTabCtrl.InsertItem(1, &musicItem); + _tcscpy(tabItem.pszText, GetResourceString(IDS_MUSIC)); + m_cTabCtrl.InsertItem(TAB_MUSIC, &tabItem); + + delete [] tabItem.pszText; + + // Create property grid (placeholder right now) + m_cPatchGrid.Create(_T("Property Grid"), WS_CHILD | WS_VISIBLE, CRect(), &m_cTabCtrl); + m_cMusicTable.Create(_T("Music Tab"), WS_CHILD, CRect(), &m_cTabCtrl); // Create run button - m_cRunBtn.Create(_T("Run"), WS_CHILD | WS_VISIBLE, CRect(), this, ID_RUN); + m_cRunBtn.Create(GetResourceString(IDS_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); + m_cKillBtn.Create(GetResourceString(IDS_KILL), WS_CHILD, CRect(), this, ID_KILL); // Set fonts SetGUIFonts(); + + // Call this after all UI objects are created because this will call LayoutObjects + static const UINT defaultWindowWidth = 32; + static const UINT defaultWindowHeight = 32; + SetWindowPos(NULL, 0, 0, m_nFontHeight * defaultWindowWidth, m_nFontHeight * defaultWindowHeight, 0); + CenterWindow(NULL); +} + +DWORD WINAPI WaitForProcessToClose(HANDLE hProcess) +{ + WaitForSingleObject(hProcess, INFINITE); + AfxGetMainWnd()->PostMessage(WM_CHILD_CLOSED, (WPARAM)hProcess); + + return 0; } void CRebuilderWindow::OnRunClick() @@ -63,8 +87,11 @@ void CRebuilderWindow::OnRunClick() if (proc) { m_lProcesses.push_back(proc); - m_cRunBtn.ShowWindow(SW_HIDE); - m_cKillBtn.ShowWindow(SW_SHOWNORMAL); + SwitchButtonMode(TRUE); + + // Register callback when process exits + DWORD threadId; // We don't use this, but Windows 95 will fail without it + CloseHandle(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WaitForProcessToClose, proc, 0, &threadId)); } } @@ -75,8 +102,12 @@ void CRebuilderWindow::OnKillClick() } m_lProcesses.clear(); - m_cKillBtn.ShowWindow(SW_HIDE); - m_cRunBtn.ShowWindow(SW_SHOWNORMAL); + SwitchButtonMode(FALSE); +} + +void CRebuilderWindow::OnSubtitleClick() +{ + ShellExecute(NULL, _T("open"), _T("http://itsmattkc.com/"), NULL, NULL, SW_SHOWNORMAL); } void CRebuilderWindow::OnSize(UINT type, int width, int height) @@ -92,7 +123,7 @@ void CRebuilderWindow::OnSize(UINT type, int width, int height) topComponentEnd += m_nFontHeight; // Bottom components - const int btnHeight = 25; + const int btnHeight = m_nFontHeight*18/10; int bottomComponentStart = height - btnHeight - padding; int bottomComponentWidth = width - dblPadding; m_cRunBtn.SetWindowPos(NULL, padding, bottomComponentStart, bottomComponentWidth, btnHeight, 0); @@ -101,15 +132,30 @@ void CRebuilderWindow::OnSize(UINT type, int width, int height) // Center components int centerHeight = bottomComponentStart - topComponentEnd; m_cTabCtrl.SetWindowPos(NULL, padding, topComponentEnd + padding, bottomComponentWidth, centerHeight - dblPadding, 0); + + // Tabs + RECT tabClientRect; + m_cTabCtrl.GetClientRect(&tabClientRect); + m_cTabCtrl.AdjustRect(FALSE, &tabClientRect); + m_cPatchGrid.SetWindowPos(NULL, tabClientRect.left, tabClientRect.top, tabClientRect.right - tabClientRect.left, tabClientRect.bottom - tabClientRect.top, 0); + m_cMusicTable.SetWindowPos(NULL, tabClientRect.left, tabClientRect.top, tabClientRect.right - tabClientRect.left, tabClientRect.bottom - tabClientRect.top, 0); } void CRebuilderWindow::OnGetMinMaxInfo(MINMAXINFO *info) { - static const LONG minimumWindowWidth = 160; - static const LONG minimumWindowHeight = 160; + static const LONG minimumWindowWidth = 12; + static const LONG minimumWindowHeight = 12; - info->ptMinTrackSize.x = minimumWindowWidth; - info->ptMinTrackSize.y = minimumWindowHeight; + info->ptMinTrackSize.x = m_nFontHeight * minimumWindowWidth; + info->ptMinTrackSize.y = m_nFontHeight * minimumWindowHeight; +} + +void CRebuilderWindow::OnTabSelChange(NMHDR *pNMHDR, LRESULT *pResult) +{ + int tab = m_cTabCtrl.GetCurSel(); + + m_cPatchGrid.ShowWindow((tab == TAB_PATCHES) ? SW_SHOWNORMAL : SW_HIDE); + m_cMusicTable.ShowWindow((tab == TAB_MUSIC) ? SW_SHOWNORMAL : SW_HIDE); } BOOL CRebuilderWindow::SetFont(HWND child, LPARAM font) @@ -118,6 +164,28 @@ BOOL CRebuilderWindow::SetFont(HWND child, LPARAM font) return true; } +LRESULT CRebuilderWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_CHILD_CLOSED) { + HANDLE hProcess = (HANDLE)wParam; + + for (std::vector::iterator it=m_lProcesses.begin(); it!=m_lProcesses.end(); it++) { + if (*it == hProcess) { + m_lProcesses.erase(it); + break; + } + } + + if (m_lProcesses.empty()) { + SwitchButtonMode(FALSE); + } + + return 0; + } else { + return super::WindowProc(uMsg, wParam, lParam); + } +} + void CRebuilderWindow::SetGUIFonts() { // Retrieve default GUI font @@ -139,6 +207,13 @@ void CRebuilderWindow::SetGUIFonts() SetFont(m_cRunBtn.GetSafeHwnd(), (LPARAM)bold); SetFont(m_cKillBtn.GetSafeHwnd(), (LPARAM)bold); + // Create link variant for subtitle + lf.lfWeight = FW_NORMAL; + lf.lfUnderline = TRUE; + + HFONT link = CreateFontIndirect(&lf); + SetFont(m_cTopLevelSubtitle.GetSafeHwnd(), (LPARAM)link); + // While here, get height of font for layout purposes HDC hDC = ::GetDC(NULL); HGDIOBJ hFontOld = SelectObject(hDC, defaultFont); @@ -149,9 +224,22 @@ void CRebuilderWindow::SetGUIFonts() ::ReleaseDC(NULL, hDC); } +void CRebuilderWindow::SwitchButtonMode(BOOL running) +{ + if (running) { + m_cRunBtn.ShowWindow(SW_HIDE); + m_cKillBtn.ShowWindow(SW_SHOWNORMAL); + } else { + m_cKillBtn.ShowWindow(SW_HIDE); + m_cRunBtn.ShowWindow(SW_SHOWNORMAL); + } +} + BEGIN_MESSAGE_MAP(CRebuilderWindow, super) ON_WM_SIZE() ON_WM_GETMINMAXINFO() ON_BN_CLICKED(ID_RUN, OnRunClick) ON_BN_CLICKED(ID_KILL, OnKillClick) + ON_BN_CLICKED(ID_SUBTITLE, OnSubtitleClick) + ON_NOTIFY(TCN_SELCHANGE, ID_TABCTRL, OnTabSelChange) END_MESSAGE_MAP() diff --git a/src/window.h b/src/window.h index 455d2eb..8a96346 100644 --- a/src/window.h +++ b/src/window.h @@ -5,6 +5,8 @@ #include #include +#include "clinkstatic.h" + class CRebuilderWindow : public CFrameWnd { public: @@ -14,31 +16,50 @@ public: afx_msg void OnKillClick(); + afx_msg void OnSubtitleClick(); + afx_msg void OnSize(UINT type, int width, int height); afx_msg void OnGetMinMaxInfo(MINMAXINFO *info); + afx_msg void OnTabSelChange(NMHDR* pNMHDR, LRESULT* pResult); + static BOOL CALLBACK SetFont(HWND child, LPARAM font); + virtual LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam); + private: void SetGUIFonts(); + void SwitchButtonMode(BOOL running); + enum { ID_RUN = 1000, ID_KILL, - ID_TABCTRL + ID_TABCTRL, + ID_SUBTITLE, + ID_PATCHGRID, + ID_COUNT + }; + + enum Tab { + TAB_PATCHES, + TAB_MUSIC }; UINT m_nFontHeight; CStatic m_cTopLevelTitle; - CStatic m_cTopLevelSubtitle; + CLinkStatic m_cTopLevelSubtitle; CTabCtrl m_cTabCtrl; CButton m_cRunBtn; CButton m_cKillBtn; + CStatic m_cPatchGrid; + CStatic m_cMusicTable; + std::vector m_lProcesses; DECLARE_MESSAGE_MAP()