This program is not shipped by Windows. Rather, it is a redistributable component shipped with some drivers, which appears to be a thin wrapper over the undocumented DriverStore* APIs exported from setupapi. Since the behaviour of this program is much easier to determine and implement, it seems much more worthwhile to do so despite not being an OS component.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=42424 Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- There is a snag, though, which is that Wine's default load order is "native,builtin" for the main EXE when loaded with an absolute path, as will pretty much always be the case for dpinst.exe. As such anyone who intends to install a package that requires it will need to manually set the program to prefer builtin. Ideally this wouldn't be necessary, but it's not clear to me how to avoid it:
* Set *dpinst.exe to have a default override of builtin,native in the DllOverrides key? There's no precedent for this, though.
* Introduce an analog of the --prefer-native switch? Seems excessive for something that would so rarely useful.
configure.ac | 2 ++ loader/wine.inf.in | 1 + programs/dpinst/Makefile.in | 4 ++++ programs/dpinst/dpinst.c | 35 +++++++++++++++++++++++++++++++++++ programs/dpinst64/Makefile.in | 5 +++++ 5 files changed, 47 insertions(+) create mode 100644 programs/dpinst/Makefile.in create mode 100644 programs/dpinst/dpinst.c create mode 100644 programs/dpinst64/Makefile.in
diff --git a/configure.ac b/configure.ac index e51e114c45e..33d2ba15ebf 100644 --- a/configure.ac +++ b/configure.ac @@ -3547,6 +3547,8 @@ WINE_CONFIG_MAKEFILE(programs/conhost/tests) WINE_CONFIG_MAKEFILE(programs/control) WINE_CONFIG_MAKEFILE(programs/cscript) WINE_CONFIG_MAKEFILE(programs/dism) +WINE_CONFIG_MAKEFILE(programs/dpinst,,[test "x$enable_win64" = xyes]) +WINE_CONFIG_MAKEFILE(programs/dpinst64,enable_dpinst,[test "x$enable_win64" = xno]) WINE_CONFIG_MAKEFILE(programs/dplaysvr) WINE_CONFIG_MAKEFILE(programs/dpnsvr) WINE_CONFIG_MAKEFILE(programs/dpvsetup) diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 0e488d87dcb..4ad9dc09efd 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -2646,6 +2646,7 @@ HKLM,%CurrentVersion%\Telephony\Country List\998,"SameAreaRule",,"G" 11,,services.exe,- 11,,spoolsv.exe,- 11,,winemenubuilder.exe,- +11,,dpinst.exe,- ; registration order matters for these 11,,msxml.dll 11,,msxml2.dll diff --git a/programs/dpinst/Makefile.in b/programs/dpinst/Makefile.in new file mode 100644 index 00000000000..bd97b860ce7 --- /dev/null +++ b/programs/dpinst/Makefile.in @@ -0,0 +1,4 @@ +MODULE = dpinst.exe +EXTRADLLFLAGS = -mconsole -municode + +C_SRCS = dpinst.c diff --git a/programs/dpinst/dpinst.c b/programs/dpinst/dpinst.c new file mode 100644 index 00000000000..52b4119760b --- /dev/null +++ b/programs/dpinst/dpinst.c @@ -0,0 +1,35 @@ +/* + * Driver Store installer replacement + * + * Copyright 2022 Zebediah Figura + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "windef.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(dpinst); + +int __cdecl wmain(int argc, WCHAR *argv[]) +{ + int i; + + for (i = 0; i < argc; i++) + FIXME(" %s", debugstr_w(argv[i])); + FIXME("\n"); + + return 0; +} diff --git a/programs/dpinst64/Makefile.in b/programs/dpinst64/Makefile.in new file mode 100644 index 00000000000..f79ed3e0dba --- /dev/null +++ b/programs/dpinst64/Makefile.in @@ -0,0 +1,5 @@ +MODULE = dpinst64.exe +EXTRADLLFLAGS = -mconsole -municode +PARENTSRC = ../dpinst + +C_SRCS = dpinst.c
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- programs/dpinst/Makefile.in | 1 + programs/dpinst/dpinst.c | 352 +++++++++++++++++++++++++++++++++- programs/dpinst64/Makefile.in | 1 + 3 files changed, 351 insertions(+), 3 deletions(-)
diff --git a/programs/dpinst/Makefile.in b/programs/dpinst/Makefile.in index bd97b860ce7..64c9c015dad 100644 --- a/programs/dpinst/Makefile.in +++ b/programs/dpinst/Makefile.in @@ -1,4 +1,5 @@ MODULE = dpinst.exe +IMPORTS = setupapi EXTRADLLFLAGS = -mconsole -municode
C_SRCS = dpinst.c diff --git a/programs/dpinst/dpinst.c b/programs/dpinst/dpinst.c index 52b4119760b..afcadffec53 100644 --- a/programs/dpinst/dpinst.c +++ b/programs/dpinst/dpinst.c @@ -18,18 +18,364 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <io.h> #include "windef.h" +#include "winbase.h" +#include "winreg.h" +#include "winuser.h" +#include "setupapi.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(dpinst);
-int __cdecl wmain(int argc, WCHAR *argv[]) +#ifdef __i386__ +static const WCHAR platform_extension[] = L"NTx86"; +#elif defined(__x86_64__) +static const WCHAR platform_extension[] = L"NTamd64"; +#elif defined(__arm__) +static const WCHAR platform_extension[] = L"NTarm"; +#elif defined(__aarch64__) +static const WCHAR platform_extension[] = L"NTarm64"; +#endif + +struct options +{ + const WCHAR *path; +}; + +static bool version_is_compatible(const WCHAR *version) +{ + size_t len = wcslen(version); + const WCHAR *p; + + /* We are only concerned with architecture. */ + if ((p = wcschr(version, '.'))) + len = p - version; + + if (!wcsnicmp(version, L"NT", len)) + return true; + + return !wcsnicmp(version, platform_extension, len); +} + +static WCHAR *concat_path(const WCHAR *root, const WCHAR *path) +{ + size_t len = wcslen(root) + wcslen(path) + 2; + WCHAR *ret = malloc(len * sizeof(WCHAR)); + + swprintf(ret, len, L"%s\%s", root, path); + return ret; +} + +static WCHAR *get_string_field(INFCONTEXT *ctx, DWORD index) +{ + WCHAR *ret; + DWORD len; + + if (!SetupGetStringFieldW(ctx, index, NULL, 0, &len) || len <= 1) + return NULL; + + ret = malloc(len * sizeof(WCHAR)); + SetupGetStringFieldW(ctx, index, ret, len, NULL); + return ret; +} + +typedef struct +{ + ULONG Unknown[6]; + ULONG State[5]; + ULONG Count[2]; + UCHAR Buffer[64]; +} SHA_CTX; + +void WINAPI A_SHAInit(SHA_CTX *ctx); +void WINAPI A_SHAUpdate(SHA_CTX *ctx, const UCHAR *buffer, UINT size); +void WINAPI A_SHAFinal(SHA_CTX *ctx, ULONG *result); + +static WCHAR *get_dst_root(const WCHAR *filename, const WCHAR *filename_abs) +{ + /* This is not compatible with Microsoft's naming scheme. We omit the + * architecture (which is redundant) and the locale (which is basically + * also redundant; in practice it is always "default"). We also don't use + * the same hash algorithm. */ + + unsigned char buffer[1024]; + unsigned char hash[20]; + unsigned int i; + SHA_CTX ctx; + HANDLE file; + WCHAR *name; + DWORD size; + + file = CreateFileW(filename_abs, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + if (file != INVALID_HANDLE_VALUE) + { + A_SHAInit(&ctx); + + do + { + if (!ReadFile(file, buffer, sizeof(buffer), &size, NULL)) + { + ERR("Failed to read from %s, error %lu.\n", debugstr_w(filename_abs), GetLastError()); + break; + } + A_SHAUpdate(&ctx, buffer, size); + } while (size); + + A_SHAFinal(&ctx, (ULONG *)hash); + CloseHandle(file); + } + else + { + ERR("Failed to open %s, error %lu.\n", debugstr_w(filename_abs), GetLastError()); + } + + name = malloc((wcslen(filename) + 1 + 40 + 1) * sizeof(WCHAR)); + wcscpy(name, filename); + wcscat(name, L"_"); + for (i = 0; i < ARRAY_SIZE(hash); ++i) + swprintf(name + wcslen(name), 3, L"%02x", hash[i]); + + return concat_path(L"C:\windows\system32\driverstore\filerepository", name); +} + +static void get_source_info(HINF hinf, const WCHAR *src_file, WCHAR **desc, WCHAR **tag, WCHAR **subdir) +{ + WCHAR *file_subdir, *disk_subdir; + INFCONTEXT file_ctx, disk_ctx; + int id, diskid; + + /* find the SourceDisksFiles entry */ + if (!SetupFindFirstLineW(hinf, L"SourceDisksFiles", src_file, &file_ctx)) return; + if (!SetupGetIntField(&file_ctx, 1, &diskid)) return; + + /* now find the diskid in the SourceDisksNames section */ + if (!SetupFindFirstLineW(hinf, L"SourceDisksNames", NULL, &disk_ctx)) return; + for (;;) + { + if (SetupGetIntField(&disk_ctx, 0, &id) && id == diskid) break; + if (!SetupFindNextLine(&disk_ctx, &disk_ctx)) return; + } + + *desc = get_string_field(&disk_ctx, 1); + *tag = get_string_field(&disk_ctx, 2); + disk_subdir = get_string_field(&disk_ctx, 4); + file_subdir = get_string_field(&file_ctx, 2); + + if (disk_subdir) + { + if (file_subdir) + *subdir = concat_path(disk_subdir, file_subdir); + else + *subdir = disk_subdir; + } + else + { + *subdir = file_subdir; + } +} + +struct copy_context +{ + HINF hinf; + HSPFILEQ queue; + const WCHAR *src_root, *dst_root; +}; + +static void queue_copy_file(const struct copy_context *copy_ctx, const WCHAR *file) +{ + SP_FILE_COPY_PARAMS_W params = + { + .cbSize = sizeof(params), + .QueueHandle = copy_ctx->queue, + .SourceRootPath = copy_ctx->src_root, + .CopyStyle = SP_COPY_NODECOMP, + .SourceFilename = file, + .TargetFilename = file, + }; + + WCHAR *desc = NULL, *tag = NULL, *subdir = NULL; + + get_source_info(copy_ctx->hinf, file, &desc, &tag, &subdir); + params.SourceDescription = desc; + params.SourceTagfile = tag; + params.SourcePath = subdir; + + if (subdir) + params.TargetDirectory = concat_path(copy_ctx->dst_root, subdir); + else + params.TargetDirectory = copy_ctx->dst_root; + + TRACE("Queueing copy from subdir %s, filename %s.\n", debugstr_w(subdir), debugstr_w(file)); + if (!SetupQueueCopyIndirectW(¶ms)) + ERR("Failed to queue copy, error %lu.\n", GetLastError()); +} + +static void queue_copy_section(const struct copy_context *copy_ctx, const WCHAR *section) +{ + if (section[0] == '@') + { + queue_copy_file(copy_ctx, section + 1); + } + else + { + INFCONTEXT context; + + if (!SetupFindFirstLineW(copy_ctx->hinf, section, NULL, &context)) return; + do + { + WCHAR *file; + + if (!(file = get_string_field(&context, 2))) + file = get_string_field(&context, 1); + + queue_copy_file(copy_ctx, file); + } while (SetupFindNextLine(&context, &context)); + } +} + +static void install_driver(const struct copy_context *copy_ctx, const WCHAR *driver_section) +{ + INFCONTEXT ctx; + DWORD i, count; + BOOL found; + + TRACE("Installing driver section %s.\n", debugstr_w(driver_section)); + + found = SetupFindFirstLineW(copy_ctx->hinf, driver_section, L"CopyFiles", &ctx); + while (found) + { + count = SetupGetFieldCount(&ctx); + + for (i = 1; i <= count; ++i) + queue_copy_section(copy_ctx, get_string_field(&ctx, i)); + + found = SetupFindNextMatchLineW(&ctx, L"CopyFiles", &ctx); + } +} + +static void install_inf(const WCHAR *root, const WCHAR *filename) +{ + WCHAR version[LINE_LEN], mfg_key[LINE_LEN], manufacturer[LINE_LEN]; + struct copy_context copy_ctx; + WCHAR *filename_abs; + void *default_ctx; + INFCONTEXT ctx; + DWORD i, j; + + filename_abs = concat_path(root, filename); + + TRACE("Installing drivers from %s.\n", debugstr_w(filename_abs)); + + if ((copy_ctx.hinf = SetupOpenInfFileW(filename_abs, NULL, INF_STYLE_WIN4, NULL)) == INVALID_HANDLE_VALUE) + { + ERR("Failed to open %s, error %lu.\n", debugstr_w(filename_abs), GetLastError()); + return; + } + copy_ctx.queue = SetupOpenFileQueue(); + copy_ctx.src_root = root; + copy_ctx.dst_root = get_dst_root(filename, filename_abs); + + for (i = 0; SetupGetLineByIndexW(copy_ctx.hinf, L"Manufacturer", i, &ctx); ++i) + { + SetupGetStringFieldW(&ctx, 0, manufacturer, ARRAY_SIZE(manufacturer), NULL); + if (!SetupGetStringFieldW(&ctx, 1, mfg_key, ARRAY_SIZE(mfg_key), NULL)) + wcscpy(mfg_key, manufacturer); + + if (SetupGetFieldCount(&ctx) >= 2) + { + BOOL compatible = FALSE; + for (j = 2; SetupGetStringFieldW(&ctx, j, version, ARRAY_SIZE(version), NULL); ++j) + { + if (version_is_compatible(version)) + { + compatible = TRUE; + break; + } + } + if (!compatible) + continue; + } + + if (!SetupDiGetActualSectionToInstallW(copy_ctx.hinf, mfg_key, mfg_key, ARRAY_SIZE(mfg_key), NULL, NULL)) + { + WARN("Failed to find section for %s, skipping.\n", debugstr_w(mfg_key)); + continue; + } + + for (j = 0; SetupGetLineByIndexW(copy_ctx.hinf, mfg_key, j, &ctx); ++j) + install_driver(©_ctx, get_string_field(&ctx, 1)); + } + + default_ctx = SetupInitDefaultQueueCallback(NULL); + if (!SetupCommitFileQueueW(NULL, copy_ctx.queue, SetupDefaultQueueCallbackW, default_ctx)) + ERR("Failed to commit queue, error %lu.\n", GetLastError()); + + SetupCloseFileQueue(copy_ctx.queue); + SetupCloseInfFile(copy_ctx.hinf); +} + +static bool parse_options(struct options *options, int argc, WCHAR **argv) { int i;
+ for (i = 1; i < argc; i++) + { + if (!wcsicmp(argv[i], L"/path")) + { + if (i >= argc - 1) + { + ERR("No argument specified for /path.\n"); + return false; + } + options->path = argv[++i]; + } + else if (argv[i][0] == '/') + { + FIXME("Ignoring option %s.\n", debugstr_w(argv[i])); + } + else + { + ERR("Invalid argument %s.\n", debugstr_w(argv[i])); + return false; + } + } + + return true; +} + +int __cdecl wmain(int argc, WCHAR *argv[]) +{ + struct options options = {0}; + struct _wfinddata_t data; + intptr_t handle; + int i; + for (i = 0; i < argc; i++) - FIXME(" %s", debugstr_w(argv[i])); - FIXME("\n"); + TRACE(" %s", debugstr_w(argv[i])); + TRACE("\n"); + + if (!parse_options(&options, argc, argv)) + return 0; + + if (!(handle = _wfindfirst(concat_path(options.path, L"*"), &data))) + { + ERR("Failed to enumerate the contents of %s.\n", debugstr_w(options.path)); + return 0; + } + + do + { + size_t len = wcslen(data.name); + + TRACE("%s\n", debugstr_w(data.name)); + + if (len >= 4 && !wcsicmp(data.name + len - 4, L".inf")) + install_inf(options.path, data.name); + } while (!_wfindnext(handle, &data));
return 0; } diff --git a/programs/dpinst64/Makefile.in b/programs/dpinst64/Makefile.in index f79ed3e0dba..29cc6c50046 100644 --- a/programs/dpinst64/Makefile.in +++ b/programs/dpinst64/Makefile.in @@ -1,4 +1,5 @@ MODULE = dpinst64.exe +IMPORTS = setupapi EXTRADLLFLAGS = -mconsole -municode PARENTSRC = ../dpinst
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- programs/dpinst/dpinst.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/programs/dpinst/dpinst.c b/programs/dpinst/dpinst.c index afcadffec53..f883dcb8689 100644 --- a/programs/dpinst/dpinst.c +++ b/programs/dpinst/dpinst.c @@ -260,6 +260,7 @@ static void install_driver(const struct copy_context *copy_ctx, const WCHAR *dri static void install_inf(const WCHAR *root, const WCHAR *filename) { WCHAR version[LINE_LEN], mfg_key[LINE_LEN], manufacturer[LINE_LEN]; + SP_FILE_COPY_PARAMS_W params = {0}; struct copy_context copy_ctx; WCHAR *filename_abs; void *default_ctx; @@ -310,6 +311,26 @@ static void install_inf(const WCHAR *root, const WCHAR *filename) install_driver(©_ctx, get_string_field(&ctx, 1)); }
+ params.cbSize = sizeof(params); + params.QueueHandle = copy_ctx.queue; + params.SourceRootPath = copy_ctx.src_root; + params.SourceFilename = filename; + params.TargetDirectory = copy_ctx.dst_root; + params.TargetFilename = filename; + params.CopyStyle = SP_COPY_NODECOMP; + if (!SetupQueueCopyIndirectW(¶ms)) + ERR("Failed to queue copy, error %lu.\n", GetLastError()); + + if (SetupFindFirstLineW(copy_ctx.hinf, L"Version", L"CatalogFile", &ctx) + && (params.SourceFilename = get_string_field(&ctx, 1))) + { + TRACE("Copying catalog file %s.\n", debugstr_w(params.SourceFilename)); + + params.TargetFilename = params.SourceFilename; + if (!SetupQueueCopyIndirectW(¶ms)) + ERR("Failed to queue copy, error %lu.\n", GetLastError()); + } + default_ctx = SetupInitDefaultQueueCallback(NULL); if (!SetupCommitFileQueueW(NULL, copy_ctx.queue, SetupDefaultQueueCallbackW, default_ctx)) ERR("Failed to commit queue, error %lu.\n", GetLastError());
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- programs/dpinst/dpinst.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/programs/dpinst/dpinst.c b/programs/dpinst/dpinst.c index f883dcb8689..72fac1638b8 100644 --- a/programs/dpinst/dpinst.c +++ b/programs/dpinst/dpinst.c @@ -337,6 +337,9 @@ static void install_inf(const WCHAR *root, const WCHAR *filename)
SetupCloseFileQueue(copy_ctx.queue); SetupCloseInfFile(copy_ctx.hinf); + + if (!SetupCopyOEMInfW(concat_path(copy_ctx.dst_root, filename), NULL, SPOST_NONE, 0, NULL, 0, NULL, NULL)) + ERR("Failed to copy INF, error %lu.\n", GetLastError()); }
static bool parse_options(struct options *options, int argc, WCHAR **argv)