work on new hooking system

This commit is contained in:
itsmattkc 2021-09-18 14:28:15 -07:00
parent 63dd568e64
commit 18be36348f
18 changed files with 848 additions and 96 deletions

View file

@ -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)

27
lib/dllmain.cpp Normal file
View file

@ -0,0 +1,27 @@
#include <IOSTREAM>
#include <TCHAR.H>
#include <WINDOWS.H>
#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;
}

115
lib/hooks.cpp Normal file
View file

@ -0,0 +1,115 @@
#include "hooks.h"
#include <STDIO.H>
#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;
}

32
lib/hooks.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef HOOKS_H
#define HOOKS_H
#include <DDRAW.H>
#include <WINDOWS.H>
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

6
lib/mmpassthru.cpp Normal file
View file

@ -0,0 +1,6 @@
#include <WINDOWS.H>
extern "C" __declspec(dllexport) DWORD rbldGetTime()
{
return timeGetTime();
}

139
lib/util.cpp Normal file
View file

@ -0,0 +1,139 @@
#include "util.h"
#include <STDIO.H>
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; i<maxOffset; i++) {
LPVOID offset = (LPVOID)((UINT_PTR)(mbi.BaseAddress)+i);
if (!memcmp(offset, search, count)) {
// Found pattern, overwrite it
memcpy(offset, replace, count);
instances++;
if (only_once) {
break;
}
}
}
// Restore original permissions
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProtec, &oldProtec);
}
}
addr += mbi.RegionSize;
}
return instances;
}
LPVOID OverwriteImport(LPVOID imageBase, LPCSTR overrideFunction, LPVOID override)
{
// Get DOS header
PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)imageBase;
// Retrieve NT header
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((UINT_PTR)imageBase + dosHeaders->e_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);
}

16
lib/util.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef UTIL_H
#define UTIL_H
#include <WINDOWS.H>
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

45
lib/worker.cpp Normal file
View file

@ -0,0 +1,45 @@
#include "worker.h"
#include <VECTOR>
#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;
}

8
lib/worker.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef WORKER_H
#define WORKER_H
#include <WINDOWS.H>
DWORD WINAPI Patch();
#endif // WORKER_H

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="com.itsmattkc.Rebuilder" type="win32" />
<description>LEGO Island modding tool and launcher.</description>
<dependency>
@ -7,4 +7,17 @@
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false">
</requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View file

@ -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"

15
res/resource.h Normal file
View file

@ -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

55
src/clinkstatic.cpp Normal file
View file

@ -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()

22
src/clinkstatic.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef CLINKSTATIC_H
#define CLINKSTATIC_H
#include <AFXWIN.H>
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

View file

@ -3,50 +3,68 @@
#include <COMMDLG.H>
#include <SHLWAPI.H>
#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;
}

View file

@ -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);
};

View file

@ -1,60 +1,84 @@
#include "window.h"
#include <WINDOWS.H>
#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<HANDLE>::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()

View file

@ -5,6 +5,8 @@
#include <AFXWIN.H>
#include <VECTOR>
#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<HANDLE> m_lProcesses;
DECLARE_MESSAGE_MAP()