#define _CRT_SECURE_NO_WARNINGS // installer.c — Self-contained Windows installer for codeMAX // // Uses Win32 PropertySheet wizard for the UI, embeds payload EXEs as // resources, and handles PATH, Start Menu, and Add/Remove Programs. // // When invoked with /uninstall, runs the uninstaller instead. #include #include #include #include #include #include #include #include #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "ole32.lib") #pragma comment(lib, "shell32.lib") #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "uuid.lib") #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #include "installer.h" // Use codebase types typedef unsigned char U8; typedef unsigned short U16; typedef unsigned int U32; typedef unsigned long long U64; typedef int S32; typedef int B32; #define ArrayCount(a) (sizeof(a) / sizeof((a)[0])) //////////////////////////////// // Globals static wchar_t g_install_dir[MAX_PATH]; static B32 g_add_to_path = 1; static HINSTANCE g_hinst; //////////////////////////////// // Helpers static B32 extract_resource(S32 resource_id, const wchar_t *dest_path) { HRSRC res = FindResourceW(NULL, MAKEINTRESOURCEW(resource_id), (LPCWSTR)RT_RCDATA); if (!res) return 0; HGLOBAL h = LoadResource(NULL, res); if (!h) return 0; void *data = LockResource(h); DWORD size = SizeofResource(NULL, res); if (!data || size == 0) return 0; HANDLE f = CreateFileW(dest_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (f == INVALID_HANDLE_VALUE) return 0; DWORD written; WriteFile(f, data, size, &written, NULL); CloseHandle(f); return written == size; } static B32 create_directory_recursive(const wchar_t *path) { wchar_t tmp[MAX_PATH]; wcscpy_s(tmp, MAX_PATH, path); for (wchar_t *p = tmp + 3; *p; p++) { // skip "C:\" if (*p == L'\\' || *p == L'/') { *p = 0; CreateDirectoryW(tmp, NULL); *p = L'\\'; } } return CreateDirectoryW(tmp, NULL) || GetLastError() == ERROR_ALREADY_EXISTS; } static B32 create_shortcut(const wchar_t *lnk_path, const wchar_t *target, const wchar_t *work_dir, const wchar_t *description, const wchar_t *icon_path) { HRESULT hr; IShellLinkW *sl = NULL; hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (void **)&sl); if (FAILED(hr)) return 0; sl->lpVtbl->SetPath(sl, target); sl->lpVtbl->SetWorkingDirectory(sl, work_dir); if (description) sl->lpVtbl->SetDescription(sl, description); if (icon_path) sl->lpVtbl->SetIconLocation(sl, icon_path, 0); IPersistFile *pf = NULL; hr = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void **)&pf); B32 ok = 0; if (SUCCEEDED(hr)) { ok = SUCCEEDED(pf->lpVtbl->Save(pf, lnk_path, TRUE)); pf->lpVtbl->Release(pf); } sl->lpVtbl->Release(sl); return ok; } static B32 path_add(const wchar_t *dir) { HKEY key; if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_READ | KEY_WRITE, &key) != ERROR_SUCCESS) return 0; wchar_t buf[8192] = {0}; DWORD buf_size = sizeof(buf) - sizeof(wchar_t); DWORD type = REG_EXPAND_SZ; RegQueryValueExW(key, L"Path", NULL, &type, (BYTE *)buf, &buf_size); // Check if already present if (wcsstr(buf, dir)) { RegCloseKey(key); return 1; } // Append S32 len = (S32)wcslen(buf); if (len > 0 && buf[len - 1] != L';') wcscat_s(buf, ArrayCount(buf), L";"); wcscat_s(buf, ArrayCount(buf), dir); RegSetValueExW(key, L"Path", 0, type, (BYTE *)buf, (DWORD)((wcslen(buf) + 1) * sizeof(wchar_t))); RegCloseKey(key); return 1; } static void path_remove(const wchar_t *dir) { HKEY key; if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_READ | KEY_WRITE, &key) != ERROR_SUCCESS) return; wchar_t buf[8192] = {0}; DWORD buf_size = sizeof(buf) - sizeof(wchar_t); DWORD type = REG_EXPAND_SZ; RegQueryValueExW(key, L"Path", NULL, &type, (BYTE *)buf, &buf_size); wchar_t result[8192] = {0}; wchar_t *ctx = NULL; wchar_t copy[8192]; wcscpy_s(copy, ArrayCount(copy), buf); wchar_t *tok = wcstok_s(copy, L";", &ctx); B32 first = 1; while (tok) { if (_wcsicmp(tok, dir) != 0) { if (!first) wcscat_s(result, ArrayCount(result), L";"); wcscat_s(result, ArrayCount(result), tok); first = 0; } tok = wcstok_s(NULL, L";", &ctx); } RegSetValueExW(key, L"Path", 0, type, (BYTE *)result, (DWORD)((wcslen(result) + 1) * sizeof(wchar_t))); RegCloseKey(key); } static void broadcast_env_change(void) { SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL); } //////////////////////////////// // In-memory dialog template builder typedef struct { U8 data[4096]; S32 len; } DlgBuf; static void dlg_align(DlgBuf *b, S32 align) { while (b->len % align) b->data[b->len++] = 0; } static void dlg_write(DlgBuf *b, const void *src, S32 n) { memcpy(b->data + b->len, src, n); b->len += n; } static void dlg_write16(DlgBuf *b, U16 v) { dlg_write(b, &v, 2); } static void dlg_write32(DlgBuf *b, U32 v) { dlg_write(b, &v, 4); } static void dlg_write_wstr(DlgBuf *b, const wchar_t *s) { S32 n = (S32)((wcslen(s) + 1) * sizeof(wchar_t)); dlg_write(b, s, n); } static DLGTEMPLATE *build_page_template(DlgBuf *b, S32 w, S32 h) { b->len = 0; // DLGTEMPLATE DLGTEMPLATE *dt = (DLGTEMPLATE *)b->data; dlg_write32(b, WS_CHILD | WS_VISIBLE | DS_SHELLFONT); // style dlg_write32(b, 0); // dwExtendedStyle dlg_write16(b, 0); // cdit (control count, filled later) dlg_write16(b, 0); // x dlg_write16(b, 0); // y dlg_write16(b, (U16)w); // cx dlg_write16(b, (U16)h); // cy dlg_write16(b, 0); // menu (none) dlg_write16(b, 0); // class (default) dlg_write_wstr(b, L""); // title // DS_SHELLFONT font dlg_write16(b, 9); // point size dlg_write_wstr(b, L"Segoe UI"); return dt; } static void add_control(DlgBuf *b, DLGTEMPLATE *dt, U32 style, S32 x, S32 y, S32 w, S32 h, S32 id, const wchar_t *cls, const wchar_t *text) { dlg_align(b, 4); // DLGITEMTEMPLATE dlg_write32(b, style | WS_CHILD | WS_VISIBLE); // style dlg_write32(b, 0); // dwExtendedStyle dlg_write16(b, (U16)x); dlg_write16(b, (U16)y); dlg_write16(b, (U16)w); dlg_write16(b, (U16)h); dlg_write16(b, (U16)id); dlg_write_wstr(b, cls); dlg_write_wstr(b, text); dlg_write16(b, 0); // creation data dt->cdit++; } //////////////////////////////// // Page IDs for controls #define IDC_DIR_EDIT 1001 #define IDC_DIR_BROWSE 1002 #define IDC_PATH_CHK 1003 #define IDC_PROGRESS 1004 #define IDC_STATUS 1005 #define IDC_LAUNCH_CHK 1006 //////////////////////////////// // Wizard pages static INT_PTR CALLBACK welcome_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: return TRUE; case WM_NOTIFY: { NMHDR *nm = (NMHDR *)lp; if (nm->code == PSN_SETACTIVE) { PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_NEXT); SetWindowLongPtrW(dlg, DWLP_MSGRESULT, 0); } return TRUE; } } return FALSE; } static INT_PTR CALLBACK dir_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: SetDlgItemTextW(dlg, IDC_DIR_EDIT, g_install_dir); CheckDlgButton(dlg, IDC_PATH_CHK, g_add_to_path ? BST_CHECKED : BST_UNCHECKED); return TRUE; case WM_COMMAND: if (LOWORD(wp) == IDC_DIR_BROWSE) { BROWSEINFOW bi = {0}; bi.hwndOwner = dlg; bi.lpszTitle = L"Select installation directory:"; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; LPITEMIDLIST pidl = SHBrowseForFolderW(&bi); if (pidl) { wchar_t path[MAX_PATH]; SHGetPathFromIDListW(pidl, path); wcscat_s(path, MAX_PATH, L"\\codeMAX"); SetDlgItemTextW(dlg, IDC_DIR_EDIT, path); CoTaskMemFree(pidl); } } return TRUE; case WM_NOTIFY: { NMHDR *nm = (NMHDR *)lp; if (nm->code == PSN_SETACTIVE) PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_BACK | PSWIZB_NEXT); if (nm->code == PSN_WIZNEXT) { GetDlgItemTextW(dlg, IDC_DIR_EDIT, g_install_dir, MAX_PATH); g_add_to_path = (IsDlgButtonChecked(dlg, IDC_PATH_CHK) == BST_CHECKED); } return TRUE; } } return FALSE; } static B32 do_install(HWND dlg) { HWND prog = GetDlgItem(dlg, IDC_PROGRESS); HWND status = GetDlgItem(dlg, IDC_STATUS); SendMessageW(prog, PBM_SETRANGE, 0, MAKELPARAM(0, 7)); SendMessageW(prog, PBM_SETSTEP, 1, 0); // 1. Create install directory SetWindowTextW(status, L"Creating install directory..."); create_directory_recursive(g_install_dir); SendMessageW(prog, PBM_STEPIT, 0, 0); // 2. Extract terminal exe SetWindowTextW(status, L"Installing codemax.exe..."); wchar_t path[MAX_PATH]; _snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir); if (!extract_resource(IDR_EXE_GUI, path)) { MessageBoxW(dlg, L"Failed to extract codemax.exe", L"Error", MB_ICONERROR); return 0; } SendMessageW(prog, PBM_STEPIT, 0, 0); // 4. Copy self as uninstaller SetWindowTextW(status, L"Creating uninstaller..."); wchar_t self_path[MAX_PATH]; GetModuleFileNameW(NULL, self_path, MAX_PATH); _snwprintf(path, MAX_PATH, L"%s\\uninstall.exe", g_install_dir); CopyFileW(self_path, path, FALSE); SendMessageW(prog, PBM_STEPIT, 0, 0); // 5. Add to PATH (optional) if (g_add_to_path) { SetWindowTextW(status, L"Updating PATH..."); path_add(g_install_dir); } SendMessageW(prog, PBM_STEPIT, 0, 0); // 6. Create Start Menu shortcuts SetWindowTextW(status, L"Creating shortcuts..."); CoInitialize(NULL); { wchar_t programs[MAX_PATH]; SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, programs); wchar_t menu_dir[MAX_PATH]; _snwprintf(menu_dir, MAX_PATH, L"%s\\codeMAX", programs); CreateDirectoryW(menu_dir, NULL); wchar_t exe_path[MAX_PATH]; _snwprintf(exe_path, MAX_PATH, L"%s\\codemax.exe", g_install_dir); wchar_t lnk[MAX_PATH]; _snwprintf(lnk, MAX_PATH, L"%s\\codeMAX.lnk", menu_dir); create_shortcut(lnk, exe_path, g_install_dir, L"codeMAX Text Editor", exe_path); } CoUninitialize(); SendMessageW(prog, PBM_STEPIT, 0, 0); // 7. Register in Add/Remove Programs SetWindowTextW(status, L"Registering application..."); { HKEY key; RegCreateKeyExA(HKEY_LOCAL_MACHINE, CODEMAX_UNINSTALL_KEY, 0, NULL, 0, KEY_WRITE, NULL, &key, NULL); char dir_a[MAX_PATH], uninst[MAX_PATH * 2]; WideCharToMultiByte(CP_UTF8, 0, g_install_dir, -1, dir_a, MAX_PATH, NULL, NULL); snprintf(uninst, sizeof(uninst), "\"%s\\uninstall.exe\" /uninstall", dir_a); RegSetValueExA(key, "DisplayName", 0, REG_SZ, (BYTE *)CODEMAX_DISPLAY_NAME, (DWORD)strlen(CODEMAX_DISPLAY_NAME) + 1); RegSetValueExA(key, "DisplayVersion", 0, REG_SZ, (BYTE *)CODEMAX_VERSION, (DWORD)strlen(CODEMAX_VERSION) + 1); RegSetValueExA(key, "Publisher", 0, REG_SZ, (BYTE *)CODEMAX_PUBLISHER, (DWORD)strlen(CODEMAX_PUBLISHER) + 1); RegSetValueExA(key, "InstallLocation", 0, REG_SZ, (BYTE *)dir_a, (DWORD)strlen(dir_a) + 1); RegSetValueExA(key, "UninstallString", 0, REG_SZ, (BYTE *)uninst, (DWORD)strlen(uninst) + 1); char icon[MAX_PATH + 4]; snprintf(icon, sizeof(icon), "%s\\codemax.exe,0", dir_a); RegSetValueExA(key, "DisplayIcon", 0, REG_SZ, (BYTE *)icon, (DWORD)strlen(icon) + 1); DWORD one = 1; RegSetValueExA(key, "NoModify", 0, REG_DWORD, (BYTE *)&one, sizeof(one)); RegSetValueExA(key, "NoRepair", 0, REG_DWORD, (BYTE *)&one, sizeof(one)); RegCloseKey(key); } SendMessageW(prog, PBM_STEPIT, 0, 0); broadcast_env_change(); SetWindowTextW(status, L"Installation complete."); return 1; } static INT_PTR CALLBACK progress_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: return TRUE; case WM_NOTIFY: { NMHDR *nm = (NMHDR *)lp; if (nm->code == PSN_SETACTIVE) { // Disable all buttons during install PropSheet_SetWizButtons(GetParent(dlg), 0); // Run install if (do_install(dlg)) { PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_NEXT); } else { PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_BACK); } SetWindowLongPtrW(dlg, DWLP_MSGRESULT, 0); } return TRUE; } } return FALSE; } static INT_PTR CALLBACK finish_proc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: CheckDlgButton(dlg, IDC_LAUNCH_CHK, BST_CHECKED); return TRUE; case WM_NOTIFY: { NMHDR *nm = (NMHDR *)lp; if (nm->code == PSN_SETACTIVE) { PropSheet_SetWizButtons(GetParent(dlg), PSWIZB_FINISH); } if (nm->code == PSN_WIZFINISH) { if (IsDlgButtonChecked(dlg, IDC_LAUNCH_CHK) == BST_CHECKED) { wchar_t exe[MAX_PATH]; _snwprintf(exe, MAX_PATH, L"%s\\codemax.exe", g_install_dir); ShellExecuteW(NULL, L"open", exe, NULL, g_install_dir, SW_SHOWNORMAL); } } return TRUE; } } return FALSE; } //////////////////////////////// // Wizard launcher static void run_installer(void) { // Default install dir wcscpy_s(g_install_dir, MAX_PATH, L"C:\\Program Files\\codeMAX"); InitCommonControls(); // Build page templates in memory DlgBuf bufs[4] = {0}; S32 page_w = 317, page_h = 143; // Page 0: Welcome DLGTEMPLATE *dt0 = build_page_template(&bufs[0], page_w, page_h); add_control(&bufs[0], dt0, SS_LEFT, 10, 10, 297, 40, -1, L"Static", L"Welcome to the codeMAX installer.\n\n" L"This will install codeMAX on your computer."); add_control(&bufs[0], dt0, SS_LEFT, 10, 60, 297, 20, -1, L"Static", L"Click Next to continue."); // Page 1: Directory + options DLGTEMPLATE *dt1 = build_page_template(&bufs[1], page_w, page_h); add_control(&bufs[1], dt1, SS_LEFT, 10, 10, 297, 10, -1, L"Static", L"Choose the installation directory:"); add_control(&bufs[1], dt1, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP, 10, 28, 230, 14, IDC_DIR_EDIT, L"Edit", L""); add_control(&bufs[1], dt1, BS_PUSHBUTTON | WS_TABSTOP, 248, 27, 60, 14, IDC_DIR_BROWSE, L"Button", L"Browse..."); add_control(&bufs[1], dt1, BS_AUTOCHECKBOX | WS_TABSTOP, 10, 52, 250, 14, IDC_PATH_CHK, L"Button", L"Add to system PATH"); // Page 2: Progress DLGTEMPLATE *dt2 = build_page_template(&bufs[2], page_w, page_h); add_control(&bufs[2], dt2, SS_LEFT, 10, 10, 297, 10, IDC_STATUS, L"Static", L"Installing..."); add_control(&bufs[2], dt2, 0, 10, 30, 297, 14, IDC_PROGRESS, PROGRESS_CLASSW, L""); // Page 3: Finish DLGTEMPLATE *dt3 = build_page_template(&bufs[3], page_w, page_h); add_control(&bufs[3], dt3, SS_LEFT, 10, 10, 297, 20, -1, L"Static", L"codeMAX has been installed successfully."); add_control(&bufs[3], dt3, BS_AUTOCHECKBOX | WS_TABSTOP, 10, 45, 200, 14, IDC_LAUNCH_CHK, L"Button", L"Launch codeMAX GUI"); PROPSHEETPAGEW pages[4] = {0}; for (S32 i = 0; i < 4; i++) { pages[i].dwSize = sizeof(PROPSHEETPAGEW); pages[i].dwFlags = PSP_DLGINDIRECT; pages[i].hInstance = g_hinst; } pages[0].pResource = dt0; pages[0].pfnDlgProc = welcome_proc; pages[1].pResource = dt1; pages[1].pfnDlgProc = dir_proc; pages[2].pResource = dt2; pages[2].pfnDlgProc = progress_proc; pages[3].pResource = dt3; pages[3].pfnDlgProc = finish_proc; PROPSHEETHEADERW psh = {0}; psh.dwSize = sizeof(PROPSHEETHEADERW); psh.dwFlags = PSH_WIZARD | PSH_PROPSHEETPAGE | PSH_USEICONID; psh.hwndParent = NULL; psh.hInstance = g_hinst; psh.pszIcon = MAKEINTRESOURCEW(IDI_CODEMAX); psh.pszCaption = L"codeMAX Setup"; psh.nPages = 4; psh.ppsp = pages; PropertySheetW(&psh); } //////////////////////////////// // Uninstaller static void run_uninstaller(void) { // Get install dir from our own exe path wchar_t self[MAX_PATH]; GetModuleFileNameW(NULL, self, MAX_PATH); wcscpy_s(g_install_dir, MAX_PATH, self); wchar_t *last_sep = wcsrchr(g_install_dir, L'\\'); if (last_sep) *last_sep = 0; S32 result = MessageBoxW(NULL, L"Are you sure you want to uninstall codeMAX?", L"codeMAX Uninstall", MB_YESNO | MB_ICONQUESTION); if (result != IDYES) return; // Delete installed files wchar_t path[MAX_PATH]; _snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir); DeleteFileW(path); _snwprintf(path, MAX_PATH, L"%s\\codemax.exe", g_install_dir); DeleteFileW(path); // Remove from PATH path_remove(g_install_dir); broadcast_env_change(); // Remove Start Menu shortcuts { wchar_t programs[MAX_PATH]; SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, programs); wchar_t menu_dir[MAX_PATH]; _snwprintf(menu_dir, MAX_PATH, L"%s\\codeMAX", programs); _snwprintf(path, MAX_PATH, L"%s\\codeMAX.lnk", menu_dir); DeleteFileW(path); _snwprintf(path, MAX_PATH, L"%s\\codeMAX GUI.lnk", menu_dir); DeleteFileW(path); RemoveDirectoryW(menu_dir); } // Remove registry entry RegDeleteKeyA(HKEY_LOCAL_MACHINE, CODEMAX_UNINSTALL_KEY); // Schedule self-deletion and remove install dir // Use cmd.exe to wait for us to exit, then delete wchar_t cmd[MAX_PATH * 3]; _snwprintf(cmd, ArrayCount(cmd), L"cmd.exe /c timeout /t 2 /nobreak >nul & del \"%s\\uninstall.exe\" & rmdir \"%s\"", g_install_dir, g_install_dir); STARTUPINFOW si = {0}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; PROCESS_INFORMATION pi = {0}; CreateProcessW(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); MessageBoxW(NULL, L"codeMAX has been uninstalled.", L"codeMAX Uninstall", MB_OK | MB_ICONINFORMATION); } //////////////////////////////// // Entry point int main(int argc, char **argv) { (void)argc; g_hinst = GetModuleHandleW(NULL); // Check for uninstall flag for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "/uninstall") == 0) { run_uninstaller(); return 0; } } // Also check via GetCommandLineW for when launched without CRT argc/argv wchar_t *cmdline = GetCommandLineW(); if (wcsstr(cmdline, L"/uninstall")) { run_uninstaller(); return 0; } run_installer(); return 0; }