This is an implementation of TODO "Need to not hard-code the dll path"
Registration is implemented with a minimal regedit engine: an array of
registry values { path, name, value } is the data source. So, all
registry entries are hard-coded in dll.c. The engine attempts to
convert values into longs to create REG_DWORD values.
msysGit (for PathToMsys) is searched in the following order:
- $(TARGET)/..
- $(TARGET)/../..
- %PATH%
- InstallLocation of uninstall info (registration-type dependant).
---
This is functionally the same patch as before, but with implemented
review comments of Johannes Schindelin (big thank you!), just in case
somebody would like to spot an issue or two with strreplace or my
style.
Makefile | 27 ++---
dll.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++---
git_shell_ext.def | 1 +
install.reg.in | 58 ----------
registry.c | 113 ++++++++++++++++++++
registry.h | 28 +++++
uninstall.reg | 27 -----
7 files changed, 443 insertions(+), 118 deletions(-)
delete mode 100644 install.reg.in
create mode 100644 registry.c
create mode 100644 registry.h
delete mode 100644 uninstall.reg
diff --git a/Makefile b/Makefile
index 4042879..dd6c1fe 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-OBJECTS=ext.o debug.o dll.o factory.o menu.o systeminfo.o
+OBJECTS=ext.o debug.o dll.o factory.o menu.o systeminfo.o registry.o
CFLAGS=-O -g
TARGET=git_shell_ext.dll
@@ -17,29 +17,24 @@ $(TARGET): $(OBJECTS) git_shell_ext.def
# gcc $(LDFLAGS) -o $@ $(OBJECTS) -lole32 -luuid -loleaut32
# dlltool -d git_shell_ext.def -l $@ $(OBJECTS)
-dll.o: dll.h ext.h factory.h
+dll.o: dll.h ext.h factory.h systeminfo.h registry.h
ext.o: ext.h debug.h
factory.o: factory.h ext.h menu.h
menu.o: menu.h ext.h debug.h systeminfo.h
systeminfo.o: systeminfo.h
+registry.o: registry.h
-inst%: inst%.reg all
- regsvr32 -s $(DLL_PATH)
- regedit -s $<
+install: all
+ regsvr32 -s -n -i:machine $(DLL_PATH)
-uninst%: uninst%.reg
- regsvr32 -u -s $(DLL_PATH)
- regedit -s $<
+uninstall: all
+ regsvr32 -u -s -n -i:machine $(DLL_PATH)
-install.reg: install.reg.in Makefile
- sed < $< > $@ \
- -e 's|@@MSYSGIT_PATH@@|$(MSYSGIT_PATH)|' \
- -e 's|@@DLL_PATH@@|$(DLL_PATH)|'
+install-user: all
+ regsvr32 -s $(DLL_PATH)
-%-user.reg: %.reg
- sed < $< > $@ \
- -e 's|HKEY_LOCAL_MACHINE\\|HKEY_CURRENT_USER\\|' \
- -e
's|HKEY_CLASSES_ROOT\\|HKEY_CURRENT_USER\\Software\\Classes\\|'
+uninstall-user: all
+ regsvr32 -u -s $(DLL_PATH)
clean:
-rm -f $(OBJECTS) $(TARGET)
diff --git a/dll.c b/dll.c
index bab9d9a..f027c02 100644
--- a/dll.c
+++ b/dll.c
@@ -1,12 +1,16 @@
#include <shlobj.h>
+#include <stdio.h>
#include "dll.h"
#include "ext.h"
#include "factory.h"
+#include "systeminfo.h"
+#include "registry.h"
/*
* The following is just the necessary infrastructure for having a .dll
* which can be registered as a COM object.
*/
+static HINSTANCE hInst;
HRESULT PASCAL DllGetClassObject(REFCLSID obj_guid, REFIID factory_guid,
void **factory_handle)
@@ -25,33 +29,302 @@ HRESULT PASCAL DllCanUnloadNow(void)
return (object_count || lock_count) ? S_FALSE : S_OK;
}
-HRESULT PASCAL DllRegisterServer(void)
+BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
+{
+ hInst = instance;
+
+ if (reason == DLL_PROCESS_ATTACH) {
+ object_count = lock_count = 0;
+ DisableThreadLibraryCalls(instance);
+ }
+
+ return 1;
+}
+
+/* replaces a substring pattern with a string replacement within a string
+ the replacement occurs in-place, hence string must be large enough to
+ hold the result
+
+ the function does not handle recursive replacements, e.g.
+ strreplace ("foo", "bar", "another bar"); // unexpected results
+
+ always returns *string
+*/
+static char *strreplace(char *string, const size_t size,
+ const char *pattern, const char *replacement)
+{
+ size_t len = strlen(string);
+ const size_t pattern_len = strlen(pattern);
+ const size_t replace_len = strlen(replacement);
+
+ char *found = strstr(string, pattern);
+
+ while (found) {
+ /* if the new len is greater than size, bail out */
+ if (len + replace_len - pattern_len >= size)
+ return string;
+
+ if (pattern_len != replace_len)
+ memmove(found + replace_len,
+ found + pattern_len,
+ len - (found - string) - pattern_len + 1);
+ memcpy(found, replacement, replace_len);
+ len += replace_len - pattern_len;
+
+ found = strstr(string, pattern);
+ }
+
+ return string;
+}
+
+/*
+ * The following is the data for our minimal regedit engine,
+ * required for registration/unregistration of the extension
+ */
+#define CLASS_CHEETAH CLASSES_ROOT "CLSID\\@@CLSID@@"
+#define CONTEXTMENUHANDLER "shellex\\ContextMenuHandlers\\@@PROGRAM_NAME@@"
+
+static const char *get_module_filename() {
+ static char module_filename[MAX_PATH] = { '\0' };
+
+ if (!*module_filename) {
+ DWORD module_size;
+
+ module_size = GetModuleFileName(hInst,
+ module_filename, MAX_PATH);
+ if (0 == module_size)
+ return NULL;
+ }
+
+ return module_filename;
+}
+
+/* as per "How to: Convert Between System::Guid and _GUID" */
+static const char *get_class_id()
+{
+ static char class_id[MAX_REGISTRY_PATH] = { '\0' };
+
+ if (!*class_id) {
+ GUID guid = CLSID_git_shell_ext;
+ sprintf(class_id,
+ "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
+ guid.Data1, guid.Data2, guid.Data3,
+ guid.Data4[0], guid.Data4[1], guid.Data4[2],
+ guid.Data4[3], guid.Data4[4], guid.Data4[5],
+ guid.Data4[6], guid.Data4[7]);
+ }
+
+ return class_id;
+}
+
+/*
+ * Tries to find msysGit in the following order:
+ * .. and ../.. (relative to the module)
+ * %PATH%
+ * as qgit (via InstallLocation of Git)
+ SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation
+ */
+static const reg_value registry_info[] = {
+ { CURRENT_WINDOWS APPROVED_EXT, "@@CLSID@@", "@@PROGRAM_NAME@@" },
+ { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
+ NULL, NULL },
+ { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
+ NULL,"@@PROGRAM_NAME@@" },
+ { CLASS_CHEETAH, NULL, NULL },
+ { CLASS_CHEETAH, NULL, "@@PROGRAM_NAME@@" },
+ { CLASS_CHEETAH "\\InProcServer32", NULL, NULL },
+ { CLASS_CHEETAH "\\InProcServer32", NULL, "@@PROGRAM_PATH@@"},
+ { CLASS_CHEETAH "\\InProcServer32", "ThreadingModel", "Apartment" },
+ { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, NULL },
+ { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
+ { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, NULL },
+ { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
+ { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
+ NULL, NULL },
+ { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
+ NULL, "@@CLSID@@" },
+ { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, NULL },
+ { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@"},
+ { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, NULL },
+ { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
+ { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
+ NULL, NULL },
+ { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
+ NULL, "@@CLSID@@" },
+ { GIT_CHEETAH_REG_PATH, NULL, NULL },
+ { GIT_CHEETAH_REG_PATH,
+ GIT_CHEETAH_REG_PATHTOMSYS, "@@MSYSGIT_PATH@@" },
+ { NULL, NULL, NULL }
+};
+
+static const reg_value debug_info[] = {
+ { CURRENT_WINDOWS "Explorer", "DesktopProcess", "1" },
+ { CURRENT_WINDOWS "Explorer\\AlwaysUnloadDll", NULL, NULL },
+ { CURRENT_WINDOWS "Explorer\\Advanced", "SeparateProcess", "1" },
+ { NULL, NULL, NULL }
+};
+
+static char msysgit[MAX_PATH] = { '\0' };
+
+static BOOL find_msysgit_in_path()
{
- char module[MAX_PATH];
- wchar_t module_name[MAX_PATH];
- ITypeLib *typelib = NULL;
+ char *file; /* file part of the path to git.exe */
+ DWORD dwFound; /* length of path to git.exe */
- GetModuleFileName(NULL, module, MAX_PATH);
- MultiByteToWideChar(CP_ACP, 0, module, -1, module_name, MAX_PATH);
- if (LoadTypeLib(module_name, &typelib) == S_OK) {
- HRESULT result = RegisterTypeLib(typelib, module_name, NULL);
- typelib->lpVtbl->Release(typelib);
- return result;
+ dwFound = SearchPath(NULL, "git.exe", NULL, MAX_PATH, msysgit, &file);
+ /* if git is not in the PATH or its path is too long */
+ if (0 == dwFound ||
+ dwFound > MAX_PATH)
+ return FALSE;
+
+ /*
+ * git.exe is in "\bin\" from what we really need
+ * the minimal case we can handle is c:\bin\git.exe
+ * otherwise declare failure
+ */
+ if (file < msysgit + 7)
+ return FALSE;
+ if (strnicmp(file - 5, "\\bin\\", 5))
+ return FALSE;
+ file[-5] = '\0';
+
+ return TRUE;
+}
+
+static BOOL find_msysgit_relative(const char *path)
+{
+ char *c;
+
+ strcpy(msysgit, get_module_filename());
+ c = strrchr(msysgit, '\\');
+ c[1] = '\0';
+ strcat(msysgit, path);
+ strcat(msysgit, "\\bin\\git.exe");
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(msysgit)) {
+ msysgit[0] = '\0'; // restore the original result
+ return FALSE;
+ }
+
+ c[1] = '\0';
+ strcat(msysgit, path);
+ return TRUE;
+}
+
+static BOOL find_msysgit_uninstall(HKEY root)
+{
+ HKEY key;
+ HRESULT result;
+ DWORD valuelen = MAX_PATH;
+
+ result = RegOpenKeyEx(root,
+ CURRENT_WINDOWS "\\Uninstall\\Git_is1",
+ 0, KEY_READ, &key);
+ if (ERROR_SUCCESS != result)
+ return FALSE;
+
+ result = RegQueryValue(key, "InstallLocation",
+ (LPBYTE)msysgit, &valuelen);
+ return ERROR_SUCCESS == result;
+}
+
+static HKEY setup_root;
+
+static const char *find_msysgit()
+{
+ if ('\0' == msysgit[0]) {
+ if (find_msysgit_relative(".."))
+ return msysgit;
+
+ if (find_msysgit_relative("..\\.."))
+ return msysgit;
+
+ if (find_msysgit_in_path())
+ return msysgit;
+
+ if (setup_root)
+ find_msysgit_uninstall(setup_root);
}
- return 1;
+
+ return msysgit;
+}
+
+/*
+ * required by registry.c
+ * supports @@PROGRAM_NAME@@, @@PROGRAM_PATH@@, @@CLSID@@ patterns
+ */
+char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH])
+{
+ if (NULL == src)
+ return NULL;
+
+ strcpy(dst, src);
+ strreplace(dst, MAX_REGISTRY_PATH,
+ "@@PROGRAM_NAME@@", program_name);
+ strreplace(dst, MAX_REGISTRY_PATH,
+ "@@PROGRAM_PATH@@", get_module_filename());
+ strreplace(dst, MAX_REGISTRY_PATH,
+ "@@CLSID@@", get_class_id());
+ strreplace(dst, MAX_REGISTRY_PATH,
+ "@@MSYSGIT_PATH@@", find_msysgit());
+
+ return dst;
+}
+
+HRESULT PASCAL DllRegisterServer(void)
+{
+ setup_root = HKEY_CURRENT_USER;
+ return create_reg_entries (setup_root, registry_info);
}
HRESULT PASCAL DllUnregisterServer(void)
{
- return S_OK;
+ setup_root = HKEY_CURRENT_USER;
+ return delete_reg_entries(setup_root, registry_info);
}
-BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
+/* provide means to create/delete keys:
+ - described in Debugging with The Shell;
+ - machine-wide registration and debugging info
+
+ possible combinations of regsvr32 command line options:
+ -n (absent) (present)
+ -i:
+ (absent) user reg (invalid)
+ debug user reg+debug user debug
+ machine user+machine reg machine reg
+ machinedebug user+machine reg+debug machine reg+debug
+
+ Obviously missing option is "machine debug". To accomplish:
+ - execute "regsvr32 -n -i:machinedebug" and then
+ - regsvr32 -u -n -i:machine
+*/
+HRESULT PASCAL DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
{
- if (reason == DLL_PROCESS_ATTACH) {
- object_count = lock_count = 0;
- DisableThreadLibraryCalls(instance);
+ BOOL bDebug = NULL != wcsstr(pszCmdLine, L"debug");
+ HRESULT result = ERROR_SUCCESS;
+
+ setup_root = wcsstr(pszCmdLine, L"machine") ?
+ HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+
+ if (bInstall) {
+ if (bDebug)
+ result = create_reg_entries(setup_root, debug_info);
+
+ /* for user-only registration, use DllRegister */
+ if (ERROR_SUCCESS == result &&
+ HKEY_LOCAL_MACHINE == setup_root)
+ result = create_reg_entries(setup_root,
+ registry_info);
+ } else { /* uninstall */
+ if (bDebug)
+ result = delete_reg_entries(setup_root, debug_info);
+
+ /* for user-only unregistration, use DllUnregister */
+ if (ERROR_SUCCESS == result &&
+ HKEY_LOCAL_MACHINE == setup_root)
+ result = delete_reg_entries(setup_root,
+ registry_info);
}
- return 1;
+ return result;
}
diff --git a/git_shell_ext.def b/git_shell_ext.def
index 472c184..bea88da 100644
--- a/git_shell_ext.def
+++ b/git_shell_ext.def
@@ -5,3 +5,4 @@ DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
+DllInstall PRIVATE
diff --git a/install.reg.in b/install.reg.in
deleted file mode 100644
index 2eb8f13..0000000
--- a/install.reg.in
+++ /dev/null
@@ -1,58 +0,0 @@
-Windows Registry Editor Version 5.00
-
-; This registry file creates neccessary entries for installation.
-; **** If you change this file, keep uninstall.reg in sync! ****
-;
-; This file is slated for being generated and not hard-coded.
-
-[HKEY_LOCAL_MACHINE\SOFTWARE\Git-Cheetah]
-
-[HKEY_LOCAL_MACHINE\SOFTWARE\Git-Cheetah]
-"PathToMsys"="@@MSYSGIT_PATH@@"
-
-[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
Extensions\Approved]
-"{ca586c80-7c84-4b88-8537-726724df6929}"="Git-Cheetah"
-
-[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
Extensions\Approved\{ca586c80-7c84-4b88-8537-726724df6929}]
[EMAIL PROTECTED]"Git-Cheetah"
-
-[HKEY_CLASSES_ROOT\CLSID\{ca586c80-7c84-4b88-8537-726724df6929}]
-
-[HKEY_CLASSES_ROOT\CLSID\{ca586c80-7c84-4b88-8537-726724df6929}]
[EMAIL PROTECTED]"Git-Cheetah"
-
-[HKEY_CLASSES_ROOT\CLSID\{ca586c80-7c84-4b88-8537-726724df6929}\InProcServer32]
[EMAIL PROTECTED]"@@DLL_PATH@@"
-
-[HKEY_CLASSES_ROOT\CLSID\{ca586c80-7c84-4b88-8537-726724df6929}\InProcServer32]
-"ThreadingModel"="Apartment"
-
-[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
-
-[HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
-
-[HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
-
-[HKEY_CLASSES_ROOT\Drive\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\Drive\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
-
-[HKEY_CLASSES_ROOT\Folder\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\Folder\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
-
-[HKEY_CLASSES_ROOT\InternetShortcut\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[HKEY_CLASSES_ROOT\InternetShortcut\shellex\ContextMenuHandlers\Git-Cheetah]
[EMAIL PROTECTED]"{ca586c80-7c84-4b88-8537-726724df6929}"
diff --git a/registry.c b/registry.c
new file mode 100644
index 0000000..57aab5a
--- /dev/null
+++ b/registry.c
@@ -0,0 +1,113 @@
+#include <windows.h>
+#include "registry.h"
+
+/* uses get_registry_path to replace patterns */
+HRESULT create_reg_entries(const HKEY root, reg_value const info[])
+{
+ HRESULT result;
+ int i;
+
+ for (i = 0; NULL != info[i].path; ++i) {
+ char path[MAX_REGISTRY_PATH];
+ char name[MAX_REGISTRY_PATH], *regname = NULL;
+ char value [MAX_REGISTRY_PATH], *regvalue = NULL;
+
+ HKEY key;
+ DWORD disp;
+
+ get_registry_path(info[i].path, path);
+ result = RegCreateKeyEx(root, path,
+ 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
+ &key, &disp);
+ if (ERROR_SUCCESS != result)
+ return (result);
+
+ regname = get_registry_path(info[i].name, name);
+ regvalue = get_registry_path(info[i].value, value);
+
+ /*
+ * regname can legitimately be NULL,
+ * but if value is NULL, it's just a key
+ */
+ if (NULL != regvalue) {
+ char *endptr;
+ DWORD dwValue = strtoul(regvalue, &endptr, 10);
+ if (endptr && !*endptr)
+ result = RegSetValueEx(key, regname, 0,
+ REG_DWORD,
+ (LPBYTE)&dwValue,
+ sizeof(dwValue));
+ else
+ result = RegSetValueEx(key, regname, 0,
+ REG_SZ,
+ (LPBYTE)regvalue,
+ (DWORD)strlen(regvalue));
+ }
+
+ RegCloseKey(key);
+ if (ERROR_SUCCESS != result)
+ return (result);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static inline HRESULT mask_errors(HRESULT const result)
+{
+ switch (result) {
+ case ERROR_FILE_NOT_FOUND: return ERROR_SUCCESS;
+ }
+
+ return result;
+}
+
+HRESULT delete_reg_entries(HKEY const root, reg_value const info[])
+{
+ HRESULT result;
+ int i = 0;
+
+ /* count items in the array */
+ while (NULL != info[i++].path);
+ /* at this point, i is the __count__, so
+ make it an offset to the last element */
+ --i;
+
+ /* walk the array backwards (we're at the terminating triple-null) */
+ do {
+ char path[MAX_REGISTRY_PATH];
+ HKEY key;
+
+ --i;
+
+ get_registry_path(info[i].path, path);
+
+ if (info[i].name || info[i].value) {
+ /* delete the value */
+
+ char name[MAX_REGISTRY_PATH], *regname = NULL;
+
+ result = mask_errors(RegOpenKeyEx(root, path,
+ 0, KEY_WRITE, &key));
+ if (ERROR_SUCCESS != result)
+ return result;
+
+ /*
+ * some of our errors are masked (e.g. not found)
+ * don't work on this key if we could not open it
+ */
+ if (NULL == key)
+ continue;
+
+ regname = get_registry_path(info[i].name, name);
+ result = mask_errors(RegDeleteValue(key, regname));
+
+ RegCloseKey(key);
+ } else /* not the value, delete the key */
+ result = mask_errors(RegDeleteKey(root, path));
+
+ if (ERROR_SUCCESS != result)
+ return (result);
+ } while (i);
+
+ return ERROR_SUCCESS;
+}
diff --git a/registry.h b/registry.h
new file mode 100644
index 0000000..2149a8c
--- /dev/null
+++ b/registry.h
@@ -0,0 +1,28 @@
+/*
+ * This is basically a simplified regedit engine that supports
+ * custom patterns through get_registry_path() that is required
+ * to be provided by the clients.
+ *
+ * It attempts to convert values to LONG to create REG_DWORD values
+ */
+
+#define MAX_REGISTRY_PATH MAX_PATH
+
+#define CURRENT_WINDOWS "Software\\Microsoft\\Windows\\CurrentVersion\\"
+#define APPROVED_EXT "Shell Extensions\\Approved"
+#define CLASSES_ROOT "Software\\Classes\\"
+
+typedef struct reg_value {
+ char *path;
+ char *name;
+ char *value;
+} reg_value;
+
+/*
+ * Clients need to provide the implementation of this function.
+ * The simplest implementation includes just strcpy(dst, src);
+ */
+char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH]);
+
+HRESULT create_reg_entries(const HKEY root, reg_value const info[]);
+HRESULT delete_reg_entries(HKEY const root, reg_value const info[]);
diff --git a/uninstall.reg b/uninstall.reg
deleted file mode 100644
index bd33685..0000000
--- a/uninstall.reg
+++ /dev/null
@@ -1,27 +0,0 @@
-Windows Registry Editor Version 5.00
-
-; This registry file creates neccessary entries for uninstallation.
-; **** If you change this file, keep install.reg in sync! ****
-;
-; This file is slated for being generated and not hard-coded.
-
-[-HKEY_LOCAL_MACHINE\SOFTWARE\Git-Cheetah]
-
-[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
Extensions\Approved]
-"{ca586c80-7c84-4b88-8537-726724df6929}"=-
-
-[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
Extensions\Approved\{ca586c80-7c84-4b88-8537-726724df6929}]
-
-[-HKEY_CLASSES_ROOT\CLSID\{ca586c80-7c84-4b88-8537-726724df6929}]
-
-[-HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[-HKEY_CLASSES_ROOT\Directory\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[-HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[-HKEY_CLASSES_ROOT\Drive\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[-HKEY_CLASSES_ROOT\Folder\shellex\ContextMenuHandlers\Git-Cheetah]
-
-[-HKEY_CLASSES_ROOT\InternetShortcut\shellex\ContextMenuHandlers\Git-Cheetah]
--
1.5.4.rc0.929.g50e2