From 0dccfbcec9818047c7fec959005453d022905835 Mon Sep 17 00:00:00 2001
From: Sebastian Lackner <sebastian@fds-team.de>
Date: Sun, 18 Jan 2015 05:42:10 +0100
Subject: ntoskrnl.exe/tests: Add initial driver testing framework and
 corresponding changes to Makefile system. (rev 2)

---
 aclocal.m4                                         |  31 +++
 configure.ac                                       |   2 +
 dlls/ntoskrnl.exe/tests/Makefile.in                |   8 +
 dlls/ntoskrnl.exe/tests/driver.sys/Makefile.in     |  11 +
 dlls/ntoskrnl.exe/tests/driver.sys/driver.c        | 134 ++++++++++++
 dlls/ntoskrnl.exe/tests/driver.sys/driver.h        |  28 +++
 dlls/ntoskrnl.exe/tests/driver.sys/driver.sys.spec |   1 +
 dlls/ntoskrnl.exe/tests/ntoskrnl.c                 | 226 +++++++++++++++++++++
 tools/make_makefiles                               |   9 +-
 tools/makedep.c                                    | 165 ++++++++++++++-
 10 files changed, 612 insertions(+), 3 deletions(-)
 create mode 100644 dlls/ntoskrnl.exe/tests/Makefile.in
 create mode 100644 dlls/ntoskrnl.exe/tests/driver.sys/Makefile.in
 create mode 100644 dlls/ntoskrnl.exe/tests/driver.sys/driver.c
 create mode 100644 dlls/ntoskrnl.exe/tests/driver.sys/driver.h
 create mode 100644 dlls/ntoskrnl.exe/tests/driver.sys/driver.sys.spec
 create mode 100644 dlls/ntoskrnl.exe/tests/ntoskrnl.c

diff --git a/aclocal.m4 b/aclocal.m4
index f4d7424..9d8296a 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -547,6 +547,28 @@ $ac_dir/crosstest: __builddeps__ dummy
         fi
 }
 
+wine_fn_config_resource ()
+{
+    ac_dir=$[1]
+    ac_name=$[2]
+    ac_flags=$[3]
+    ac_dll=$ac_name
+
+    case $ac_name in
+      *.*) ;;
+      *)   ac_dll=$ac_dll.dll ;;
+    esac
+
+    ac_clean=
+    test -n "$CROSSTARGET" && ac_clean=`expr $ac_dir/$ac_dll : "\\(.*\\)\\."`_crossres.`expr $ac_dll : ".*\\.\\(.*\\)"`
+    test -n "$DLLEXT" || ac_clean="$ac_dir/$ac_dll"
+
+    AS_VAR_IF([enable_tests],[no],[wine_fn_disabled_rules $ac_clean; return])
+
+    wine_fn_append_file SUBDIRS $ac_dir
+    wine_fn_clean_rules $ac_clean
+}
+
 wine_fn_config_tool ()
 {
     ac_dir=$[1]
@@ -650,6 +672,15 @@ wine_fn_config_test $1 ac_name[]ac_suffix [$2]dnl
 m4_popdef([ac_suffix])dnl
 m4_popdef([ac_name])])
 
+dnl **** Create a test resource makefile from config.status ****
+dnl
+dnl Usage: WINE_CONFIG_RESOURCE(dir,flags)
+dnl
+AC_DEFUN([WINE_CONFIG_RESOURCE],[AC_REQUIRE([WINE_CONFIG_HELPERS])dnl
+m4_pushdef([ac_name],[m4_bpatsubst([$1],[.*/\([^/\]*\)$],[\1])])dnl
+wine_fn_config_resource $1 ac_name [$2]dnl
+m4_popdef([ac_name])])
+
 dnl **** Create a static lib makefile from config.status ****
 dnl
 dnl Usage: WINE_CONFIG_LIB(name,flags)
diff --git a/configure.ac b/configure.ac
index bb390e2..d766249 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3216,6 +3216,8 @@ WINE_CONFIG_TEST(dlls/ntdll/tests)
 WINE_CONFIG_DLL(ntdsapi,,[implib])
 WINE_CONFIG_TEST(dlls/ntdsapi/tests)
 WINE_CONFIG_DLL(ntoskrnl.exe,,[implib],[ntoskrnl])
+WINE_CONFIG_TEST(dlls/ntoskrnl.exe/tests)
+WINE_CONFIG_RESOURCE(dlls/ntoskrnl.exe/tests/driver.sys)
 WINE_CONFIG_DLL(ntprint)
 WINE_CONFIG_TEST(dlls/ntprint/tests)
 WINE_CONFIG_DLL(objsel,,[clean])
diff --git a/dlls/ntoskrnl.exe/tests/Makefile.in b/dlls/ntoskrnl.exe/tests/Makefile.in
new file mode 100644
index 0000000..f0c2460
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/Makefile.in
@@ -0,0 +1,8 @@
+TESTDLL   = ntoskrnl.exe
+IMPORTS   = advapi32
+
+C_SRCS = \
+	ntoskrnl.c
+
+RC_DLLS = \
+	driver.sys
diff --git a/dlls/ntoskrnl.exe/tests/driver.sys/Makefile.in b/dlls/ntoskrnl.exe/tests/driver.sys/Makefile.in
new file mode 100644
index 0000000..bc040e4
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/driver.sys/Makefile.in
@@ -0,0 +1,11 @@
+RESOURCE   = driver.sys
+IMPORTS    = ntoskrnl
+
+WINEFLAGS  = -Wb,--subsystem,native
+CROSSFLAGS = -nostartfiles -nostdlib -nodefaultlibs \
+             -Wl,--subsystem,native \
+             -Wl,--image-base,0x10000 \
+             -Wl,-entry,_DriverEntry@8
+
+C_SRCS = \
+	driver.c
diff --git a/dlls/ntoskrnl.exe/tests/driver.sys/driver.c b/dlls/ntoskrnl.exe/tests/driver.sys/driver.c
new file mode 100644
index 0000000..35f78d1
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/driver.sys/driver.c
@@ -0,0 +1,134 @@
+/*
+ * ntoskrnl.exe testing framework
+ *
+ * Copyright 2015 Sebastian Lackner
+ * Copyright 2015 Michael Müller
+ * Copyright 2015 Christian Costa
+ *
+ * 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 <stdarg.h>
+
+#include "ntstatus.h"
+#define WIN32_NO_STATUS
+#include "windef.h"
+#include "winbase.h"
+#include "winternl.h"
+#include "winioctl.h"
+#include "ddk/wdm.h"
+
+#include "driver.h"
+
+const WCHAR driver_device[] = {'\\','D','e','v','i','c','e',
+                               '\\','W','i','n','e','T','e','s','t','D','r','i','v','e','r',0};
+const WCHAR driver_link[] = {'\\','D','o','s','D','e','v','i','c','e','s',
+                             '\\','W','i','n','e','T','e','s','t','D','r','i','v','e','r',0};
+
+
+static NTSTATUS test_basic_ioctl(IRP *irp, IO_STACK_LOCATION *stack, ULONG_PTR *info)
+{
+    const char str[] = "Wine is not an emulator";
+    ULONG length = stack->Parameters.DeviceIoControl.OutputBufferLength;
+    char *buffer = irp->AssociatedIrp.SystemBuffer;
+    int i;
+
+    if (!buffer)
+        return STATUS_ACCESS_VIOLATION;
+
+    if (length < sizeof(str)-1)
+        return STATUS_BUFFER_TOO_SMALL;
+
+    for (i = 0; i < sizeof(str)-1; i++)
+        buffer[i] = str[i];
+
+    *info = sizeof(str)-1;
+    return STATUS_SUCCESS;
+}
+
+
+static NTSTATUS WINAPI driver_Create(DEVICE_OBJECT *device, IRP *irp)
+{
+    irp->IoStatus.Status = STATUS_SUCCESS;
+    IoCompleteRequest(irp, IO_NO_INCREMENT);
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp)
+{
+    IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
+    NTSTATUS status = STATUS_NOT_SUPPORTED;
+    ULONG_PTR information = 0;
+
+    switch (stack->Parameters.DeviceIoControl.IoControlCode)
+    {
+        case IOCTL_WINETEST_BASIC_IOCTL:
+            status = test_basic_ioctl(irp, stack, &information);
+            break;
+
+        default:
+            break;
+    }
+
+    irp->IoStatus.Status = status;
+    irp->IoStatus.Information = information;
+    IoCompleteRequest(irp, IO_NO_INCREMENT);
+    return status;
+}
+
+static NTSTATUS WINAPI driver_Close(DEVICE_OBJECT *device, IRP *irp)
+{
+    irp->IoStatus.Status = STATUS_SUCCESS;
+    IoCompleteRequest(irp, IO_NO_INCREMENT);
+    return STATUS_SUCCESS;
+}
+
+static VOID WINAPI driver_Unload(DRIVER_OBJECT *driver)
+{
+    UNICODE_STRING linkW;
+
+    DbgPrint("unloading driver\n");
+
+    RtlInitUnicodeString(&linkW, driver_link);
+    IoDeleteSymbolicLink(&linkW);
+
+    IoDeleteDevice(driver->DeviceObject);
+}
+
+NTSTATUS WINAPI DriverEntry(DRIVER_OBJECT *driver, PUNICODE_STRING registry)
+{
+    UNICODE_STRING nameW, linkW;
+    DEVICE_OBJECT *device;
+    NTSTATUS status;
+
+    DbgPrint("loading driver\n");
+
+    /* Allow unloading of the driver */
+    driver->DriverUnload = driver_Unload;
+
+    /* Set driver functions */
+    driver->MajorFunction[IRP_MJ_CREATE]            = driver_Create;
+    driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]    = driver_IoControl;
+    driver->MajorFunction[IRP_MJ_CLOSE]             = driver_Close;
+
+    RtlInitUnicodeString(&nameW, driver_device);
+    RtlInitUnicodeString(&linkW, driver_link);
+
+    if (!(status = IoCreateDevice(driver, 0, &nameW, FILE_DEVICE_UNKNOWN,
+                                  FILE_DEVICE_SECURE_OPEN, FALSE, &device)))
+        status = IoCreateSymbolicLink(&linkW, &nameW);
+
+    return status;
+}
diff --git a/dlls/ntoskrnl.exe/tests/driver.sys/driver.h b/dlls/ntoskrnl.exe/tests/driver.sys/driver.h
new file mode 100644
index 0000000..372e908
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/driver.sys/driver.h
@@ -0,0 +1,28 @@
+/*
+ * ntoskrnl.exe testing framework
+ *
+ * Copyright 2015 Sebastian Lackner
+ * Copyright 2015 Michael Müller
+ * Copyright 2015 Christian Costa
+ *
+ * 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
+ */
+
+
+/*
+ * All custom IOCTLs need to have a function value >= 0x800.
+ */
+
+#define IOCTL_WINETEST_BASIC_IOCTL 			CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
diff --git a/dlls/ntoskrnl.exe/tests/driver.sys/driver.sys.spec b/dlls/ntoskrnl.exe/tests/driver.sys/driver.sys.spec
new file mode 100644
index 0000000..76421d7
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/driver.sys/driver.sys.spec
@@ -0,0 +1 @@
+# nothing to export
diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c
new file mode 100644
index 0000000..9b8a6a7
--- /dev/null
+++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c
@@ -0,0 +1,226 @@
+/*
+ * ntoskrnl.exe testing framework
+ *
+ * Copyright 2015 Sebastian Lackner
+ * Copyright 2015 Michael Müller
+ * Copyright 2015 Christian Costa
+ *
+ * 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 "windows.h"
+#include "winsvc.h"
+#include "winioctl.h"
+#include "wine/test.h"
+
+#include "driver.sys/driver.h"
+
+static const char driver_name[] = "WineTestDriver";
+static const char device_path[] = "\\\\.\\WineTestDriver";
+
+/* extracts a driver from a resource to a filename */
+static BOOL extract_resource(const char *name, const char *filename)
+{
+    DWORD size, written = 0;
+    HGLOBAL reshandle;
+    HRSRC resinfo;
+    HANDLE file;
+    char *data;
+
+    resinfo = FindResourceA(NULL, name, (LPCSTR)RT_RCDATA);
+    if (!resinfo)
+        return FALSE;
+
+    reshandle = LoadResource(NULL, resinfo);
+    if (!reshandle)
+        return FALSE;
+
+    data = LockResource(reshandle);
+    if (!data)
+        return FALSE;
+
+    size = SizeofResource(NULL, resinfo);
+    if (!size)
+        return FALSE;
+
+    file = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (file == INVALID_HANDLE_VALUE)
+        return FALSE;
+
+    while (size)
+    {
+        if (!WriteFile(file, data, size, &written, NULL))
+        {
+            CloseHandle(file);
+            return FALSE;
+        }
+
+        data += written;
+        size -= written;
+    }
+
+    CloseHandle(file);
+    return TRUE;
+}
+
+static void wait_driver(SC_HANDLE service, SERVICE_STATUS *status)
+{
+    for (;;)
+    {
+        Sleep(100);
+
+        if (!QueryServiceStatus(service, status))
+        {
+            ok(0, "QueryServiceStatus failed with %x\n", GetLastError());
+            status->dwCurrentState = SERVICE_STOPPED;
+            return;
+        }
+
+        if (status->dwCurrentState != SERVICE_STOP_PENDING &&
+            status->dwCurrentState != SERVICE_START_PENDING)
+            return;
+    }
+}
+
+/* unload a driver and delete the temporary file */
+static void unload_driver(SC_HANDLE service, const char *filename)
+{
+    SERVICE_STATUS status;
+
+    if (service)
+    {
+        /* Wine specific hack - when the test is the first application
+         * in a wineprefix, then the driver will need some time to start up,
+         * before we can stop them again. */
+        wait_driver(service, &status);
+
+        ControlService(service, SERVICE_CONTROL_STOP, &status);
+        wait_driver(service, &status);
+        ok(status.dwCurrentState == SERVICE_STOPPED,
+           "expected SERVICE_STOPPED, got %d\n", status.dwCurrentState);
+
+        DeleteService(service);
+        CloseServiceHandle(service);
+    }
+    if (filename)
+    {
+        ok(DeleteFileA(filename),
+           "DeleteFileA failed with %x\n", GetLastError());
+    }
+}
+
+/* load a driver and return the service handle */
+static SC_HANDLE load_driver(char *filename)
+{
+    SC_HANDLE manager, service;
+    SERVICE_STATUS status;
+    char temp[MAX_PATH];
+
+    manager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+    if (!manager)
+        return FALSE;
+
+    /* before we start with the actual tests, make sure to terminate
+     * any old wine test drivers. */
+    service = OpenServiceA(manager, driver_name, SERVICE_ALL_ACCESS);
+    if (service) unload_driver(service, NULL);
+
+    /* extract the new driver which is embedded into a resource */
+    GetTempPathA(sizeof(temp), temp);
+    GetTempFileNameA(temp, "drv", 0, filename);
+
+    if (!extract_resource("driver.sys", filename))
+    {
+        ok(0, "Failed to extract driver resource\n");
+        goto err;
+    }
+
+    trace("Trying to load driver %s\n", filename);
+
+    /* load the new driver */
+    service = CreateServiceA(manager, driver_name, driver_name,
+                             SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
+                             SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+                             filename, NULL, NULL, NULL, NULL, NULL);
+    if (!service)
+    {
+        ok(0, "CreateServiceA failed with %x\n", GetLastError());
+        goto err;
+    }
+
+    ok(StartServiceA(service, 0, NULL),
+       "StartServiceA failed with %x\n", GetLastError());
+
+    CloseServiceHandle(manager);
+
+    /* wait for the service to start up properly */
+    wait_driver(service, &status);
+    ok(status.dwCurrentState == SERVICE_RUNNING,
+       "expected SERVICE_RUNNING, got %d\n", status.dwCurrentState);
+
+    if (status.dwCurrentState != SERVICE_RUNNING)
+    {
+        unload_driver(service, filename);
+        return NULL;
+    }
+
+    return service;
+
+err:
+    CloseServiceHandle(manager);
+    unload_driver(NULL, filename);
+    return NULL;
+}
+
+static void test_basic_ioctl(void)
+{
+    const char str[] = "Wine is not an emulator";
+    DWORD bytes_returned;
+    char buf[32];
+    HANDLE file;
+    BOOL res;
+
+    file = CreateFileA(device_path, GENERIC_READ | GENERIC_WRITE,
+                       0, NULL, OPEN_EXISTING, 0, NULL);
+    if (file == INVALID_HANDLE_VALUE)
+    {
+        ok(0, "Connecting to driver failed with %x\n", GetLastError());
+        return;
+    }
+
+    res = DeviceIoControl(file, IOCTL_WINETEST_BASIC_IOCTL, NULL, 0, buf,
+                          sizeof(buf), &bytes_returned, NULL);
+    ok(res, "DeviceIoControl failed with %x\n", GetLastError());
+    ok(bytes_returned == sizeof(str)-1, "Unexpected number of bytes\n");
+    ok(!memcmp(buf, str, sizeof(str)-1), "Unexpected response data\n");
+
+    CloseHandle(file);
+}
+
+START_TEST(ntoskrnl)
+{
+    char filename[MAX_PATH];
+    SC_HANDLE service;
+
+    if (!(service = load_driver(filename)))
+    {
+        win_skip("Failed to load driver, skipping tests.\n");
+        return;
+    }
+
+    test_basic_ioctl();
+
+    unload_driver(service, filename);
+}
diff --git a/tools/make_makefiles b/tools/make_makefiles
index ce38c53..d60a3fd 100755
--- a/tools/make_makefiles
+++ b/tools/make_makefiles
@@ -204,7 +204,7 @@ sub parse_makefile($)
         {
             die "Configure substitution is not allowed in $file" unless $file eq "Makefile";
         }
-        if (/^\s*(MODULE|IMPORTLIB|TESTDLL|PARENTSRC|APPMODE)\s*=\s*(.*)/)
+        if (/^\s*(MODULE|IMPORTLIB|TESTDLL|RESOURCE|PARENTSRC|APPMODE)\s*=\s*(.*)/)
         {
             my $var = $1;
             $make{$var} = $2;
@@ -440,6 +440,13 @@ sub update_makefiles(@)
             (my $dir = $file) =~ s/^(.*)\/Makefile/$1/;
             push @lines, "WINE_CONFIG_TEST($dir$flag_args)\n";
         }
+        elsif (defined($make{"RESOURCE"}))  # test resource
+        {
+            die "MODULE should not be defined in $file" if defined $make{"MODULE"};
+            die "STATICLIB should not be defined in $file" if defined $make{"STATICLIB"};
+            (my $dir = $file) =~ s/^(.*)\/Makefile/$1/;
+            push @lines, "WINE_CONFIG_RESOURCE($dir$flag_args)\n";
+        }
         elsif (defined($make{"MODULE"}) && $make{"MODULE"} =~ /\.a$/)  # import lib
         {
             die "MODULE should not be defined as static lib in $file" unless $file =~ /^dlls\//;
diff --git a/tools/makedep.c b/tools/makedep.c
index 12f5395..a7723a0 100644
--- a/tools/makedep.c
+++ b/tools/makedep.c
@@ -175,4 +175,5 @@ struct makefile
     const char     *module;
     const char     *testdll;
+    const char     *resource;
     const char     *sharedlib;
     const char     *staticlib;
@@ -480,6 +481,30 @@ static char *get_extension( char *filename )
 
 
 /*******************************************************************
+ *         prepend_extension
+ */
+static char *prepend_extension( const char *name, const char *prepend, const char *new_ext )
+{
+    int name_len, prepend_len = strlen(prepend);
+    const char *ext;
+    char *ret;
+
+    ext = strrchr( name, '.' );
+    if (ext && strchr(ext, '/')) ext = NULL;
+    name_len = ext ? (ext - name) : strlen( name );
+
+    if (!ext) ext = new_ext;
+    if (!ext) ext = "";
+
+    ret = xmalloc( name_len + prepend_len + strlen(ext) + 1 );
+    memcpy( ret, name, name_len );
+    memcpy( ret + name_len, prepend, prepend_len );
+    strcpy( ret + name_len + prepend_len, ext );
+    return ret;
+}
+
+
+/*******************************************************************
  *         replace_extension
  */
 static char *replace_extension( const char *name, const char *old_ext, const char *new_ext )
@@ -2277,6 +2302,7 @@ static struct strarray output_sources( const struct makefile *make )
     struct strarray includes = empty_strarray;
     struct strarray phony_targets = empty_strarray;
     struct strarray all_targets = empty_strarray;
+    struct strarray resource_dlls = get_expanded_make_var_array( make, "RC_DLLS" );
     struct strarray install_rules[NB_INSTALL_RULES];
     char *ldrpath_local   = get_expanded_make_variable( make, "LDRPATH_LOCAL" );
     char *ldrpath_install = get_expanded_make_variable( make, "LDRPATH_INSTALL" );
@@ -2559,7 +2585,7 @@ static struct strarray output_sources( const struct makefile *make )
         }
         else
         {
-            int need_cross = make->testdll ||
+            int need_cross = make->testdll || make->resource ||
                 (source->file->flags & FLAG_C_IMPLIB) ||
                 (make->module && make->staticlib);
 
@@ -2573,7 +2599,7 @@ static struct strarray output_sources( const struct makefile *make )
             output_filenames( includes );
             output_filenames( make->define_args );
             output_filenames( extradefs );
-            if (make->module || make->staticlib || make->sharedlib || make->testdll)
+            if (make->module || make->staticlib || make->sharedlib || make->testdll || make->resource)
             {
                 output_filenames( dll_flags );
                 if (make->use_msvcrt) output_filenames( msvcrt_flags );
@@ -2625,6 +2651,65 @@ static struct strarray output_sources( const struct makefile *make )
         output( "\n" );
     }
 
+    /* rules for resource dlls, call Makefile in subdirectory */
+
+    if (resource_dlls.count)
+    {
+        for (i = 0; i < resource_dlls.count; i++)
+        {
+            output( "%s/%s%s: %s/Makefile\n", resource_dlls.str[i],
+                    resource_dlls.str[i], dll_ext, resource_dlls.str[i] );
+            output( "\t@cd %s && $(MAKE) %s%s\n", resource_dlls.str[i],
+                    resource_dlls.str[i], dll_ext );
+        }
+
+        output( "%s:", obj_dir_path( make, "resource_dlls.o" ));
+        for (i = 0; i < resource_dlls.count; i++)
+            output( " %s/%s%s", resource_dlls.str[i], resource_dlls.str[i], dll_ext );
+        output( "\n" );
+        output( "\t(" );
+        for (i = 0; i < resource_dlls.count; i++)
+        {
+            if (i != 0) output( "; \\\n  " );
+            output( "echo \"%s RCDATA %s/%s%s\"", resource_dlls.str[i],
+                    resource_dlls.str[i], resource_dlls.str[i], dll_ext );
+        }
+        output( ") | %s -o $@\n", tools_path( make, "wrc" ) );
+        strarray_add( &clean_files, "resource_dlls.o" );
+        strarray_add( &object_files, "resource_dlls.o" );
+
+        if (crosstarget && (make->testdll || (make->module && make->staticlib)))
+        {
+            for (i = 0; i < resource_dlls.count; i++)
+            {
+                char *name = prepend_extension( resource_dlls.str[i], "_crossres", ".dll" );
+                output( "%s/%s: %s/Makefile\n", resource_dlls.str[i], name, resource_dlls.str[i] );
+                output( "\t@cd %s && $(MAKE) %s\n", resource_dlls.str[i], name );
+                free( name );
+            }
+
+            output( "%s:", obj_dir_path( make, "resource_dlls.cross.o" ));
+            for (i = 0; i < resource_dlls.count; i++)
+            {
+                char *name = prepend_extension( resource_dlls.str[i], "_crossres", ".dll" );
+                output( " %s/%s", resource_dlls.str[i], name );
+                free( name );
+            }
+            output( "\n" );
+            output( "\t(" );
+            for (i = 0; i < resource_dlls.count; i++)
+            {
+                char *name = prepend_extension( resource_dlls.str[i], "_crossres", ".dll" );
+                if (i != 0) output( "; \\\n  " );
+                output( "echo \"%s RCDATA %s/%s\"", resource_dlls.str[i], resource_dlls.str[i], name );
+                free( name );
+            }
+            output( ") | %s -o $@\n", tools_path( make, "wrc" ) );
+            strarray_add( &clean_files, "resource_dlls.cross.o" );
+            strarray_add( &crossobj_files, "resource_dlls.cross.o" );
+        }
+    }
+
     if (make->module && !make->staticlib)
     {
         struct strarray all_libs = empty_strarray;
@@ -3024,6 +3109,81 @@ static struct strarray output_sources( const struct makefile *make )
         add_install_rule( make, install_rules, make->scripts.str[i], make->scripts.str[i],
                           strmake( "S$(bindir)/%s", make->scripts.str[i] ));
 
+    if (make->resource)
+    {
+        char *extra_wine_flags = get_expanded_make_variable( make, "WINEFLAGS" );
+        char *extra_cross_flags = get_expanded_make_variable( make, "CROSSFLAGS" );
+        struct strarray all_libs = empty_strarray;
+        char *module_path = obj_dir_path( make, make->resource );
+        char *spec_file = NULL;
+
+        if (!make->appmode.count)
+            spec_file = src_dir_path( make, replace_extension( make->resource, ".dll", ".spec" ));
+        for (i = 0; i < make->imports.count; i++)
+            strarray_add( &all_libs, strmake( "-l%s", make->imports.str[i] ));
+        strarray_addall( &all_libs, get_expanded_make_var_array( make, "LIBS" ));
+
+        if (*dll_ext)
+        {
+            strarray_add( &all_targets, strmake( "%s%s", make->resource, dll_ext ));
+            strarray_add( &all_targets, strmake( "%s.fake", make->resource ));
+            output( "%s%s %s.fake:", module_path, dll_ext, module_path );
+        }
+        else
+        {
+            strarray_add( &all_targets, make->resource );
+            output( "%s:", module_path );
+        }
+        if (spec_file) output_filename( spec_file );
+        output_filenames_obj_dir( make, object_files );
+        output_filenames_obj_dir( make, res_files );
+        output( "\n" );
+        output( "\t%s -o $@", tools_path( make, "winegcc" ));
+        output_filename( strmake( "-B%s", tools_dir_path( make, "winebuild" )));
+        if (tools_dir) output_filename( strmake( "--sysroot=%s", top_obj_dir_path( make, "" )));
+        output_filenames( target_flags );
+        output_filenames( unwind_flags );
+        if (spec_file)
+        {
+            output( " -shared %s", spec_file );
+            output_filenames( make->extradllflags );
+        }
+        else output_filenames( make->appmode );
+        if (extra_wine_flags) output( " %s", extra_wine_flags );
+        output_filenames_obj_dir( make, object_files );
+        output_filenames_obj_dir( make, res_files );
+        output_filenames( all_libs );
+        output_filename( "$(LDFLAGS)" );
+        output( "\n" );
+
+        if (crosstarget)
+        {
+            char *crossres = prepend_extension( make->resource, "_crossres", ".dll" );
+
+            strarray_add( &clean_files, crossres );
+            output( "%s:", obj_dir_path( make, crossres ));
+            output_filenames_obj_dir( make, crossobj_files );
+            output_filenames_obj_dir( make, res_files );
+            output( "\n" );
+            output( "\t%s -o $@ -b %s", tools_path( make, "winegcc" ), crosstarget );
+            output_filename( strmake( "-B%s", tools_dir_path( make, "winebuild" )));
+            if (tools_dir) output_filename( strmake( "--sysroot=%s", top_obj_dir_path( make, "" )));
+            output_filename( "--lib-suffix=.cross.a" );
+            if (spec_file)
+            {
+                output( " -shared %s", spec_file );
+                output_filenames( make->extradllflags );
+            }
+            else output_filenames( make->appmode );
+            if (extra_cross_flags) output( " %s", extra_cross_flags );
+            output_filenames_obj_dir( make, crossobj_files );
+            output_filenames_obj_dir( make, res_files );
+            output_filenames( all_libs );
+            output_filename( "$(LDFLAGS)" );
+            output( "\n" );
+        }
+    }
+
     if (!make->disabled)
     {
         if (all_targets.count)
@@ -3381,4 +3541,5 @@ static void load_sources( struct makefile *make )
     make->module        = get_expanded_make_variable( make, "MODULE" );
     make->testdll       = get_expanded_make_variable( make, "TESTDLL" );
+    make->resource      = get_expanded_make_variable( make, "RESOURCE" );
     make->sharedlib     = get_expanded_make_variable( make, "SHAREDLIB" );
     make->staticlib     = get_expanded_make_variable( make, "STATICLIB" );
-- 
2.7.1