add c and jai
This commit is contained in:
604
c/installer/installer.c
Normal file
604
c/installer/installer.c
Normal file
@@ -0,0 +1,604 @@
|
||||
#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 <windows.h>
|
||||
#include <commctrl.h>
|
||||
#include <shlobj.h>
|
||||
#include <objbase.h>
|
||||
#include <shobjidl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
16
c/installer/installer.h
Normal file
16
c/installer/installer.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
// installer.h — Shared constants for installer and resource script
|
||||
|
||||
// Resource IDs
|
||||
#define IDI_CODEMAX 101
|
||||
#define IDR_EXE_TERMINAL 201
|
||||
#define IDR_EXE_GUI 202
|
||||
|
||||
// Product info
|
||||
#define CODEMAX_APP_NAME "codemax"
|
||||
#define CODEMAX_DISPLAY_NAME "codeMAX"
|
||||
#define CODEMAX_VERSION "1.0.0"
|
||||
#define CODEMAX_PUBLISHER "codeMAX"
|
||||
|
||||
// Registry
|
||||
#define CODEMAX_UNINSTALL_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\codeMAX"
|
||||
17
c/installer/installer.manifest
Normal file
17
c/installer/installer.manifest
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="codemax.installer" version="1.0.0.0" processorArchitecture="amd64"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
14
c/installer/installer.rc
Normal file
14
c/installer/installer.rc
Normal file
@@ -0,0 +1,14 @@
|
||||
// installer.rc — Resource script for codemax installer
|
||||
// Embeds icon, UAC manifest, and payload executables.
|
||||
|
||||
#include "installer.h"
|
||||
#include <winresrc.h>
|
||||
|
||||
// Application icon
|
||||
IDI_CODEMAX ICON "assets\\icons\\codemax.ico"
|
||||
|
||||
// UAC elevation manifest
|
||||
1 24 "src\\installer\\installer.manifest"
|
||||
|
||||
// Payload executable (embedded as RCDATA)
|
||||
IDR_EXE_GUI RCDATA "build_release\\codemax.exe"
|
||||
Reference in New Issue
Block a user