2003-03-08 19:29:09 +00:00
|
|
|
/*
|
|
|
|
* ReactOS kernel
|
|
|
|
* Copyright (C) 2003 ReactOS Team
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
*
|
2009-10-27 10:34:16 +00:00
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2003-03-08 19:29:09 +00:00
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
|
|
* PROJECT: ReactOS text-mode setup
|
2015-09-13 16:40:36 +00:00
|
|
|
* FILE: base/setup/usetup/registry.c
|
2003-03-08 19:29:09 +00:00
|
|
|
* PURPOSE: Registry creation functions
|
|
|
|
* PROGRAMMER: Eric Kohl
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
|
2006-08-31 09:13:03 +00:00
|
|
|
#include "usetup.h"
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2010-06-07 21:38:49 +00:00
|
|
|
#define NDEBUG
|
2003-12-01 18:28:54 +00:00
|
|
|
#include <debug.h>
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2003-03-18 20:39:49 +00:00
|
|
|
#define FLG_ADDREG_BINVALUETYPE 0x00000001
|
|
|
|
#define FLG_ADDREG_NOCLOBBER 0x00000002
|
|
|
|
#define FLG_ADDREG_DELVAL 0x00000004
|
|
|
|
#define FLG_ADDREG_APPEND 0x00000008
|
|
|
|
#define FLG_ADDREG_KEYONLY 0x00000010
|
|
|
|
#define FLG_ADDREG_OVERWRITEONLY 0x00000020
|
|
|
|
#define FLG_ADDREG_TYPE_SZ 0x00000000
|
|
|
|
#define FLG_ADDREG_TYPE_MULTI_SZ 0x00010000
|
|
|
|
#define FLG_ADDREG_TYPE_EXPAND_SZ 0x00020000
|
|
|
|
#define FLG_ADDREG_TYPE_BINARY (0x00000000 | FLG_ADDREG_BINVALUETYPE)
|
|
|
|
#define FLG_ADDREG_TYPE_DWORD (0x00010000 | FLG_ADDREG_BINVALUETYPE)
|
|
|
|
#define FLG_ADDREG_TYPE_NONE (0x00020000 | FLG_ADDREG_BINVALUETYPE)
|
|
|
|
#define FLG_ADDREG_TYPE_MASK (0xFFFF0000 | FLG_ADDREG_BINVALUETYPE)
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2013-02-02 21:56:00 +00:00
|
|
|
#ifdef _M_IX86
|
|
|
|
#define Architecture L"x86"
|
|
|
|
#elif defined(_M_AMD64)
|
|
|
|
#define Architecture L"amd64"
|
|
|
|
#elif defined(_M_IA64)
|
|
|
|
#define Architecture L"ia64"
|
|
|
|
#elif defined(_M_ARM)
|
|
|
|
#define Architecture L"arm"
|
|
|
|
#elif defined(_M_PPC)
|
|
|
|
#define Architecture L"ppc"
|
|
|
|
#endif
|
|
|
|
|
2005-09-15 17:19:31 +00:00
|
|
|
#include <pshpack1.h>
|
|
|
|
|
|
|
|
typedef struct _REG_DISK_MOUNT_INFO
|
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
ULONG Signature;
|
|
|
|
LARGE_INTEGER StartingOffset;
|
2005-09-15 17:19:31 +00:00
|
|
|
} REG_DISK_MOUNT_INFO, *PREG_DISK_MOUNT_INFO;
|
|
|
|
|
|
|
|
#include <poppack.h>
|
2003-03-13 09:51:11 +00:00
|
|
|
|
2003-03-08 19:29:09 +00:00
|
|
|
/* FUNCTIONS ****************************************************************/
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
static
|
|
|
|
BOOLEAN
|
|
|
|
GetRootKey(
|
|
|
|
PWCHAR Name)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!_wcsicmp (Name, L"HKCR"))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
wcscpy (Name, L"\\Registry\\Machine\\SOFTWARE\\Classes\\");
|
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!_wcsicmp (Name, L"HKCU"))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
wcscpy (Name, L"\\Registry\\User\\.DEFAULT\\");
|
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!_wcsicmp (Name, L"HKLM"))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
wcscpy (Name, L"\\Registry\\Machine\\");
|
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!_wcsicmp (Name, L"HKU"))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
wcscpy (Name, L"\\Registry\\User\\");
|
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!_wcsicmp (Name, L"HKR"))
|
|
|
|
return FALSE;
|
2003-03-18 20:39:49 +00:00
|
|
|
#endif
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
return FALSE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* append_multi_sz_value
|
|
|
|
*
|
|
|
|
* Append a multisz string to a multisz registry value.
|
|
|
|
*/
|
|
|
|
#if 0
|
|
|
|
static void
|
|
|
|
append_multi_sz_value (HANDLE hkey,
|
2007-07-20 10:22:58 +00:00
|
|
|
const WCHAR *value,
|
|
|
|
const WCHAR *strings,
|
|
|
|
DWORD str_size )
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
DWORD size, type, total;
|
|
|
|
WCHAR *buffer, *p;
|
|
|
|
|
|
|
|
if (RegQueryValueExW( hkey, value, NULL, &type, NULL, &size )) return;
|
|
|
|
if (type != REG_MULTI_SZ) return;
|
|
|
|
|
|
|
|
if (!(buffer = HeapAlloc( GetProcessHeap(), 0, (size + str_size) * sizeof(WCHAR) ))) return;
|
|
|
|
if (RegQueryValueExW( hkey, value, NULL, NULL, (BYTE *)buffer, &size )) goto done;
|
|
|
|
|
|
|
|
/* compare each string against all the existing ones */
|
|
|
|
total = size;
|
|
|
|
while (*strings)
|
|
|
|
{
|
|
|
|
int len = strlenW(strings) + 1;
|
|
|
|
|
|
|
|
for (p = buffer; *p; p += strlenW(p) + 1)
|
|
|
|
if (!strcmpiW( p, strings )) break;
|
|
|
|
|
|
|
|
if (!*p) /* not found, need to append it */
|
|
|
|
{
|
|
|
|
memcpy( p, strings, len * sizeof(WCHAR) );
|
|
|
|
p[len] = 0;
|
|
|
|
total += len;
|
|
|
|
}
|
|
|
|
strings += len;
|
|
|
|
}
|
|
|
|
if (total != size)
|
|
|
|
{
|
|
|
|
TRACE( "setting value %s to %s\n", debugstr_w(value), debugstr_w(buffer) );
|
|
|
|
RegSetValueExW( hkey, value, 0, REG_MULTI_SZ, (BYTE *)buffer, total );
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
HeapFree( GetProcessHeap(), 0, buffer );
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* delete_multi_sz_value
|
|
|
|
*
|
|
|
|
* Remove a string from a multisz registry value.
|
|
|
|
*/
|
|
|
|
#if 0
|
|
|
|
static void delete_multi_sz_value( HKEY hkey, const WCHAR *value, const WCHAR *string )
|
|
|
|
{
|
|
|
|
DWORD size, type;
|
|
|
|
WCHAR *buffer, *src, *dst;
|
|
|
|
|
|
|
|
if (RegQueryValueExW( hkey, value, NULL, &type, NULL, &size )) return;
|
|
|
|
if (type != REG_MULTI_SZ) return;
|
|
|
|
/* allocate double the size, one for value before and one for after */
|
|
|
|
if (!(buffer = HeapAlloc( GetProcessHeap(), 0, size * 2 * sizeof(WCHAR) ))) return;
|
|
|
|
if (RegQueryValueExW( hkey, value, NULL, NULL, (BYTE *)buffer, &size )) goto done;
|
|
|
|
src = buffer;
|
|
|
|
dst = buffer + size;
|
|
|
|
while (*src)
|
|
|
|
{
|
|
|
|
int len = strlenW(src) + 1;
|
|
|
|
if (strcmpiW( src, string ))
|
|
|
|
{
|
|
|
|
memcpy( dst, src, len * sizeof(WCHAR) );
|
|
|
|
dst += len;
|
|
|
|
}
|
|
|
|
src += len;
|
|
|
|
}
|
|
|
|
*dst++ = 0;
|
|
|
|
if (dst != buffer + 2*size) /* did we remove something? */
|
|
|
|
{
|
|
|
|
TRACE( "setting value %s to %s\n", debugstr_w(value), debugstr_w(buffer + size) );
|
|
|
|
RegSetValueExW( hkey, value, 0, REG_MULTI_SZ,
|
|
|
|
(BYTE *)(buffer + size), dst - (buffer + size) );
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
HeapFree( GetProcessHeap(), 0, buffer );
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* do_reg_operation
|
|
|
|
*
|
|
|
|
* Perform an add/delete registry operation depending on the flags.
|
|
|
|
*/
|
|
|
|
static BOOLEAN
|
|
|
|
do_reg_operation(HANDLE KeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
PUNICODE_STRING ValueName,
|
|
|
|
PINFCONTEXT Context,
|
|
|
|
ULONG Flags)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
WCHAR EmptyStr = (WCHAR)0;
|
|
|
|
ULONG Type;
|
|
|
|
ULONG Size;
|
|
|
|
|
2003-03-22 15:25:03 +00:00
|
|
|
if (Flags & FLG_ADDREG_DELVAL) /* deletion */
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
#if 0
|
2003-03-22 15:25:03 +00:00
|
|
|
if (ValueName)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
RegDeleteValueW( KeyHandle, ValueName );
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
else
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
RegDeleteKeyW( KeyHandle, NULL );
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
#endif
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2003-03-22 15:25:03 +00:00
|
|
|
if (Flags & FLG_ADDREG_KEYONLY)
|
2003-03-18 20:39:49 +00:00
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
if (Flags & (FLG_ADDREG_NOCLOBBER | FLG_ADDREG_OVERWRITEONLY))
|
|
|
|
{
|
|
|
|
BOOL exists = !RegQueryValueExW( hkey, value, NULL, NULL, NULL, NULL );
|
|
|
|
if (exists && (flags & FLG_ADDREG_NOCLOBBER))
|
2007-07-20 10:22:58 +00:00
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
if (!exists & (flags & FLG_ADDREG_OVERWRITEONLY))
|
2007-07-20 10:22:58 +00:00
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
switch (Flags & FLG_ADDREG_TYPE_MASK)
|
|
|
|
{
|
|
|
|
case FLG_ADDREG_TYPE_SZ:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_SZ;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
case FLG_ADDREG_TYPE_MULTI_SZ:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_MULTI_SZ;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
case FLG_ADDREG_TYPE_EXPAND_SZ:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_EXPAND_SZ;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
case FLG_ADDREG_TYPE_BINARY:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_BINARY;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
case FLG_ADDREG_TYPE_DWORD:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_DWORD;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
case FLG_ADDREG_TYPE_NONE:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = REG_NONE;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
default:
|
2007-07-20 10:22:58 +00:00
|
|
|
Type = Flags >> 16;
|
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(Flags & FLG_ADDREG_BINVALUETYPE) ||
|
2006-08-31 09:13:03 +00:00
|
|
|
(Type == REG_DWORD && SetupGetFieldCount (Context) == 5))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
PWCHAR Str = NULL;
|
|
|
|
|
|
|
|
if (Type == REG_MULTI_SZ)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
if (!SetupGetMultiSzFieldW (Context, 5, NULL, 0, &Size))
|
|
|
|
Size = 0;
|
|
|
|
|
|
|
|
if (Size)
|
|
|
|
{
|
|
|
|
Str = (WCHAR*) RtlAllocateHeap (ProcessHeap, 0, Size * sizeof(WCHAR));
|
|
|
|
if (Str == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
SetupGetMultiSzFieldW (Context, 5, Str, Size, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Flags & FLG_ADDREG_APPEND)
|
|
|
|
{
|
|
|
|
if (Str == NULL)
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
// append_multi_sz_value( hkey, value, str, size );
|
|
|
|
|
|
|
|
RtlFreeHeap (ProcessHeap, 0, Str);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
/* else fall through to normal string handling */
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
else
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
if (!SetupGetStringFieldW (Context, 5, NULL, 0, &Size))
|
|
|
|
Size = 0;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2007-07-20 10:22:58 +00:00
|
|
|
if (Size)
|
|
|
|
{
|
|
|
|
Str = (WCHAR*) RtlAllocateHeap (ProcessHeap, 0, Size * sizeof(WCHAR));
|
|
|
|
if (Str == NULL)
|
|
|
|
return FALSE;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2007-07-20 10:22:58 +00:00
|
|
|
SetupGetStringFieldW (Context, 5, Str, Size, NULL);
|
|
|
|
}
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
if (Type == REG_DWORD)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
2012-04-22 22:40:43 +00:00
|
|
|
ULONG dw = Str ? wcstoul (Str, NULL, 0) : 0;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2007-07-20 10:22:58 +00:00
|
|
|
DPRINT("setting dword %wZ to %lx\n", ValueName, dw);
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2007-07-20 10:22:58 +00:00
|
|
|
NtSetValueKey (KeyHandle,
|
|
|
|
ValueName,
|
|
|
|
0,
|
|
|
|
Type,
|
|
|
|
(PVOID)&dw,
|
|
|
|
sizeof(ULONG));
|
2006-11-10 21:00:24 +00:00
|
|
|
#else
|
2007-07-20 10:22:58 +00:00
|
|
|
RegSetValueExW(KeyHandle, ValueName, 0, Type, (const UCHAR*)&dw, sizeof(ULONG));
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2007-07-20 10:22:58 +00:00
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
else
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
DPRINT("setting value %wZ to %S\n", ValueName, Str);
|
2003-03-19 20:12:44 +00:00
|
|
|
|
2007-07-20 10:22:58 +00:00
|
|
|
if (Str)
|
|
|
|
{
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2007-07-20 10:22:58 +00:00
|
|
|
NtSetValueKey (KeyHandle,
|
|
|
|
ValueName,
|
|
|
|
0,
|
|
|
|
Type,
|
|
|
|
(PVOID)Str,
|
|
|
|
Size * sizeof(WCHAR));
|
2006-11-10 21:00:24 +00:00
|
|
|
#else
|
2007-07-20 10:22:58 +00:00
|
|
|
RegSetValueExW(KeyHandle, ValueName, 0, Type, (const UCHAR*)Str, Size * sizeof(WCHAR));
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2007-07-20 10:22:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2007-07-20 10:22:58 +00:00
|
|
|
NtSetValueKey (KeyHandle,
|
|
|
|
ValueName,
|
|
|
|
0,
|
|
|
|
Type,
|
|
|
|
(PVOID)&EmptyStr,
|
|
|
|
sizeof(WCHAR));
|
2006-11-10 21:00:24 +00:00
|
|
|
#else
|
2007-07-20 10:22:58 +00:00
|
|
|
RegSetValueExW(KeyHandle, ValueName, 0, Type, (const UCHAR*)&EmptyStr, sizeof(WCHAR));
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2007-07-20 10:22:58 +00:00
|
|
|
}
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
RtlFreeHeap (ProcessHeap, 0, Str);
|
|
|
|
}
|
|
|
|
else /* get the binary data */
|
|
|
|
{
|
|
|
|
PUCHAR Data = NULL;
|
|
|
|
|
2006-08-31 09:13:03 +00:00
|
|
|
if (!SetupGetBinaryField (Context, 5, NULL, 0, &Size))
|
2007-07-20 10:22:58 +00:00
|
|
|
Size = 0;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
if (Size)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
Data = (unsigned char*) RtlAllocateHeap (ProcessHeap, 0, Size);
|
|
|
|
if (Data == NULL)
|
|
|
|
return FALSE;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2007-07-20 10:22:58 +00:00
|
|
|
DPRINT("setting binary data %wZ len %lu\n", ValueName, Size);
|
|
|
|
SetupGetBinaryField (Context, 5, Data, Size, NULL);
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2003-03-22 15:25:03 +00:00
|
|
|
NtSetValueKey (KeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
ValueName,
|
|
|
|
0,
|
|
|
|
Type,
|
|
|
|
(PVOID)Data,
|
|
|
|
Size);
|
2006-11-10 21:00:24 +00:00
|
|
|
#else
|
2007-07-20 10:22:58 +00:00
|
|
|
RegSetValueExW(KeyHandle, ValueName, 0, Type, (const UCHAR*)Data, Size);
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
RtlFreeHeap (ProcessHeap, 0, Data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2003-03-18 20:39:49 +00:00
|
|
|
NTSTATUS
|
|
|
|
CreateNestedKey (PHANDLE KeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
ACCESS_MASK DesiredAccess,
|
|
|
|
POBJECT_ATTRIBUTES ObjectAttributes)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
OBJECT_ATTRIBUTES LocalObjectAttributes;
|
|
|
|
UNICODE_STRING LocalKeyName;
|
|
|
|
ULONG Disposition;
|
|
|
|
NTSTATUS Status;
|
2011-06-26 10:08:31 +00:00
|
|
|
USHORT FullNameLength;
|
2003-03-18 20:39:49 +00:00
|
|
|
PWCHAR Ptr;
|
|
|
|
HANDLE LocalKeyHandle;
|
|
|
|
|
|
|
|
Status = NtCreateKey (KeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
ObjectAttributes,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
&Disposition);
|
2003-03-18 20:39:49 +00:00
|
|
|
DPRINT("NtCreateKey(%wZ) called (Status %lx)\n", ObjectAttributes->ObjectName, Status);
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
|
|
return Status;
|
|
|
|
|
|
|
|
/* Copy object attributes */
|
|
|
|
RtlCopyMemory (&LocalObjectAttributes,
|
2007-07-20 10:22:58 +00:00
|
|
|
ObjectAttributes,
|
|
|
|
sizeof(OBJECT_ATTRIBUTES));
|
2003-03-18 20:39:49 +00:00
|
|
|
RtlCreateUnicodeString (&LocalKeyName,
|
2007-07-20 10:22:58 +00:00
|
|
|
ObjectAttributes->ObjectName->Buffer);
|
2003-03-18 20:39:49 +00:00
|
|
|
LocalObjectAttributes.ObjectName = &LocalKeyName;
|
2011-06-26 10:08:31 +00:00
|
|
|
FullNameLength = LocalKeyName.Length;
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
/* Remove the last part of the key name and try to create the key again. */
|
|
|
|
while (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
|
|
{
|
|
|
|
Ptr = wcsrchr (LocalKeyName.Buffer, '\\');
|
|
|
|
if (Ptr == NULL || Ptr == LocalKeyName.Buffer)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
|
|
break;
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
*Ptr = (WCHAR)0;
|
|
|
|
LocalKeyName.Length = wcslen (LocalKeyName.Buffer) * sizeof(WCHAR);
|
|
|
|
|
|
|
|
Status = NtCreateKey (&LocalKeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&LocalObjectAttributes,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
&Disposition);
|
2003-03-18 20:39:49 +00:00
|
|
|
DPRINT("NtCreateKey(%wZ) called (Status %lx)\n", &LocalKeyName, Status);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
|
|
{
|
|
|
|
RtlFreeUnicodeString (&LocalKeyName);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add removed parts of the key name and create them too. */
|
|
|
|
while (TRUE)
|
|
|
|
{
|
2011-06-26 10:08:31 +00:00
|
|
|
if (LocalKeyName.Length == FullNameLength)
|
2007-07-20 10:22:58 +00:00
|
|
|
{
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
*KeyHandle = LocalKeyHandle;
|
|
|
|
break;
|
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
NtClose (LocalKeyHandle);
|
|
|
|
|
2011-06-26 10:08:31 +00:00
|
|
|
LocalKeyName.Buffer[LocalKeyName.Length / sizeof(WCHAR)] = L'\\';
|
|
|
|
LocalKeyName.Length = wcslen (LocalKeyName.Buffer) * sizeof(WCHAR);
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
Status = NtCreateKey (&LocalKeyHandle,
|
2007-07-20 10:22:58 +00:00
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&LocalObjectAttributes,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
&Disposition);
|
2003-03-18 20:39:49 +00:00
|
|
|
DPRINT("NtCreateKey(%wZ) called (Status %lx)\n", &LocalKeyName, Status);
|
|
|
|
if (!NT_SUCCESS(Status))
|
2007-07-20 10:22:58 +00:00
|
|
|
break;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RtlFreeUnicodeString (&LocalKeyName);
|
|
|
|
|
|
|
|
return Status;
|
|
|
|
}
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2003-03-18 20:39:49 +00:00
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* registry_callback
|
|
|
|
*
|
|
|
|
* Called once for each AddReg and DelReg entry in a given section.
|
|
|
|
*/
|
|
|
|
static BOOLEAN
|
2014-05-12 16:14:19 +00:00
|
|
|
registry_callback(HINF hInf, PCWSTR Section, BOOLEAN Delete)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
WCHAR Buffer[MAX_INF_STRING_LENGTH];
|
|
|
|
UNICODE_STRING Name;
|
|
|
|
UNICODE_STRING Value;
|
|
|
|
PUNICODE_STRING ValuePtr;
|
|
|
|
NTSTATUS Status;
|
2006-08-31 09:13:03 +00:00
|
|
|
UINT Flags;
|
2003-03-18 20:39:49 +00:00
|
|
|
ULONG Length;
|
|
|
|
|
2006-08-31 09:13:03 +00:00
|
|
|
INFCONTEXT Context;
|
2003-03-18 20:39:49 +00:00
|
|
|
HANDLE KeyHandle;
|
|
|
|
BOOLEAN Ok;
|
|
|
|
|
|
|
|
|
2006-08-31 09:13:03 +00:00
|
|
|
Ok = SetupFindFirstLineW (hInf, Section, NULL, &Context);
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2005-11-24 08:12:20 +00:00
|
|
|
if (Ok)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2006-08-31 09:13:03 +00:00
|
|
|
for (;Ok; Ok = SetupFindNextLine (&Context, &Context))
|
2005-11-24 08:12:20 +00:00
|
|
|
{
|
|
|
|
/* get root */
|
2006-08-31 09:13:03 +00:00
|
|
|
if (!SetupGetStringFieldW (&Context, 1, Buffer, MAX_INF_STRING_LENGTH, NULL))
|
2005-11-24 08:12:20 +00:00
|
|
|
continue;
|
|
|
|
if (!GetRootKey (Buffer))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* get key */
|
|
|
|
Length = wcslen (Buffer);
|
2006-08-31 09:13:03 +00:00
|
|
|
if (!SetupGetStringFieldW (&Context, 2, Buffer + Length, MAX_INF_STRING_LENGTH - Length, NULL))
|
2005-11-24 08:12:20 +00:00
|
|
|
*Buffer = 0;
|
|
|
|
|
|
|
|
DPRINT("KeyName: <%S>\n", Buffer);
|
|
|
|
|
|
|
|
/* get flags */
|
2006-08-31 09:13:03 +00:00
|
|
|
if (!SetupGetIntField (&Context, 4, (PINT)&Flags))
|
2005-11-24 08:12:20 +00:00
|
|
|
Flags = 0;
|
|
|
|
|
|
|
|
DPRINT("Flags: %lx\n", Flags);
|
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2005-11-24 08:12:20 +00:00
|
|
|
RtlInitUnicodeString (&Name,
|
|
|
|
Buffer);
|
|
|
|
|
|
|
|
InitializeObjectAttributes (&ObjectAttributes,
|
|
|
|
&Name,
|
|
|
|
OBJ_CASE_INSENSITIVE,
|
|
|
|
NULL,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (Delete || (Flags & FLG_ADDREG_OVERWRITEONLY))
|
|
|
|
{
|
|
|
|
Status = NtOpenKey (&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
|
|
{
|
2015-03-29 11:29:02 +00:00
|
|
|
DPRINT1("NtOpenKey(%wZ) failed (Status %lx)\n", &Name, Status);
|
2005-11-24 08:12:20 +00:00
|
|
|
continue; /* ignore if it doesn't exist */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Status = CreateNestedKey (&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
|
|
{
|
2015-03-29 11:29:02 +00:00
|
|
|
DPRINT1("CreateNestedKey(%wZ) failed (Status %lx)\n", &Name, Status);
|
2005-11-24 08:12:20 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2006-11-10 21:00:24 +00:00
|
|
|
#else
|
2007-07-20 10:22:58 +00:00
|
|
|
if (Delete || (Flags & FLG_ADDREG_OVERWRITEONLY))
|
|
|
|
{
|
|
|
|
LONG rc = RegOpenKeyW(NULL, Buffer, &KeyHandle);
|
|
|
|
if (rc != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
DPRINT("RegOpenKeyW(%S) failed (error %lu)\n", Buffer, rc);
|
|
|
|
continue; /* ignore if it doesn't exist */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LONG rc = RegCreateKeyW(NULL, Buffer, &KeyHandle);
|
|
|
|
if (rc != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
DPRINT("RegCreateKeyW(%S) failed (error %lu)\n", Buffer, rc);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2005-11-24 08:12:20 +00:00
|
|
|
|
|
|
|
/* get value name */
|
2006-08-31 09:13:03 +00:00
|
|
|
if (SetupGetStringFieldW (&Context, 3, Buffer, MAX_INF_STRING_LENGTH, NULL))
|
2005-11-24 08:12:20 +00:00
|
|
|
{
|
|
|
|
RtlInitUnicodeString (&Value,
|
|
|
|
Buffer);
|
|
|
|
ValuePtr = &Value;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ValuePtr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* and now do it */
|
2006-08-31 09:13:03 +00:00
|
|
|
if (!do_reg_operation (KeyHandle, ValuePtr, &Context, Flags))
|
2005-11-24 08:12:20 +00:00
|
|
|
{
|
|
|
|
NtClose (KeyHandle);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2006-11-10 21:00:24 +00:00
|
|
|
#ifdef __REACTOS__
|
2005-11-24 08:12:20 +00:00
|
|
|
NtClose (KeyHandle);
|
2006-11-10 21:00:24 +00:00
|
|
|
#endif
|
2005-11-24 08:12:20 +00:00
|
|
|
}
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOLEAN
|
2014-05-12 16:14:19 +00:00
|
|
|
ImportRegistryFile(
|
|
|
|
PWSTR Filename,
|
|
|
|
PWSTR Section,
|
|
|
|
LCID LocaleId,
|
|
|
|
BOOLEAN Delete)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
WCHAR FileNameBuffer[MAX_PATH];
|
|
|
|
HINF hInf;
|
|
|
|
UINT ErrorLine;
|
|
|
|
|
|
|
|
/* Load inf file from install media. */
|
|
|
|
wcscpy(FileNameBuffer, SourcePath.Buffer);
|
|
|
|
wcscat(FileNameBuffer, L"\\");
|
|
|
|
wcscat(FileNameBuffer, Filename);
|
|
|
|
|
|
|
|
hInf = SetupOpenInfFileW(FileNameBuffer,
|
|
|
|
NULL,
|
|
|
|
INF_STYLE_WIN4,
|
|
|
|
LocaleId,
|
|
|
|
&ErrorLine);
|
|
|
|
if (hInf == INVALID_HANDLE_VALUE)
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("SetupOpenInfFile() failed\n");
|
|
|
|
return FALSE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!registry_callback(hInf, L"AddReg", FALSE))
|
2003-03-18 20:39:49 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("registry_callback() failed\n");
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!registry_callback(hInf, L"AddReg.NT" Architecture, FALSE))
|
2013-02-02 21:56:00 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("registry_callback() failed\n");
|
2013-02-02 21:56:00 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
InfCloseFile(hInf);
|
2003-03-18 20:39:49 +00:00
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
return TRUE;
|
2003-03-18 20:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-03-19 20:12:44 +00:00
|
|
|
BOOLEAN
|
2014-05-12 16:14:19 +00:00
|
|
|
SetInstallPathValue(
|
|
|
|
PUNICODE_STRING InstallPath)
|
2003-03-19 20:12:44 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\HARDWARE");
|
|
|
|
UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"InstallPath");
|
|
|
|
HANDLE KeyHandle;
|
|
|
|
NTSTATUS Status;
|
2003-03-19 20:12:44 +00:00
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
/* Create the 'secret' InstallPath key */
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
|
|
&KeyName,
|
|
|
|
OBJ_CASE_INSENSITIVE,
|
|
|
|
NULL,
|
|
|
|
NULL);
|
|
|
|
Status = NtOpenKey(&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status))
|
2003-03-19 20:12:44 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("NtOpenKey() failed (Status %lx)\n", Status);
|
|
|
|
return FALSE;
|
2003-03-19 20:12:44 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
Status = NtSetValueKey(KeyHandle,
|
|
|
|
&ValueName,
|
|
|
|
0,
|
|
|
|
REG_SZ,
|
|
|
|
(PVOID)InstallPath->Buffer,
|
|
|
|
InstallPath->Length + sizeof(WCHAR));
|
|
|
|
NtClose(KeyHandle);
|
|
|
|
if (!NT_SUCCESS(Status))
|
2003-03-19 20:12:44 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("NtSetValueKey() failed (Status %lx)\n", Status);
|
|
|
|
return FALSE;
|
2003-03-19 20:12:44 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
return TRUE;
|
2003-03-19 20:12:44 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
|
2005-09-15 17:19:31 +00:00
|
|
|
BOOLEAN
|
2014-05-12 16:14:19 +00:00
|
|
|
SetMountedDeviceValue(
|
|
|
|
CHAR Letter,
|
|
|
|
ULONG Signature,
|
|
|
|
LARGE_INTEGER StartingOffset)
|
2005-09-15 17:19:31 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
WCHAR ValueNameBuffer[16];
|
|
|
|
UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\MountedDevices");
|
|
|
|
UNICODE_STRING ValueName;
|
|
|
|
REG_DISK_MOUNT_INFO MountInfo;
|
|
|
|
NTSTATUS Status;
|
|
|
|
HANDLE KeyHandle;
|
2005-09-15 17:19:31 +00:00
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
swprintf(ValueNameBuffer, L"\\DosDevices\\%C:", Letter);
|
|
|
|
RtlInitUnicodeString(&ValueName, ValueNameBuffer);
|
2007-01-05 20:19:21 +00:00
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
|
|
&KeyName,
|
|
|
|
OBJ_CASE_INSENSITIVE,
|
|
|
|
NULL,
|
|
|
|
NULL);
|
|
|
|
Status = NtOpenKey(&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status))
|
2005-09-15 17:19:31 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
Status = NtCreateKey(&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
REG_OPTION_NON_VOLATILE,
|
|
|
|
NULL);
|
2005-09-15 17:19:31 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
if (!NT_SUCCESS(Status))
|
2005-09-15 17:19:31 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("NtCreateKey() failed (Status %lx)\n", Status);
|
|
|
|
return FALSE;
|
2005-09-15 17:19:31 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
MountInfo.Signature = Signature;
|
|
|
|
MountInfo.StartingOffset = StartingOffset;
|
|
|
|
Status = NtSetValueKey(KeyHandle,
|
|
|
|
&ValueName,
|
|
|
|
0,
|
|
|
|
REG_BINARY,
|
|
|
|
(PVOID)&MountInfo,
|
|
|
|
sizeof(MountInfo));
|
|
|
|
NtClose(KeyHandle);
|
|
|
|
if (!NT_SUCCESS(Status))
|
2005-09-15 17:19:31 +00:00
|
|
|
{
|
2014-05-12 16:14:19 +00:00
|
|
|
DPRINT1("NtSetValueKey() failed (Status %lx)\n", Status);
|
|
|
|
return FALSE;
|
2005-09-15 17:19:31 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:14:19 +00:00
|
|
|
return TRUE;
|
2005-09-15 17:19:31 +00:00
|
|
|
}
|
|
|
|
|
2014-04-09 21:49:30 +00:00
|
|
|
|
|
|
|
VOID
|
2014-05-12 16:14:19 +00:00
|
|
|
SetDefaultPagefile(
|
|
|
|
WCHAR Drive)
|
2014-04-09 21:49:30 +00:00
|
|
|
{
|
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management");
|
|
|
|
UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"PagingFiles");
|
|
|
|
WCHAR ValueBuffer[] = L"?:\\pagefile.sys 0 0\0";
|
|
|
|
HANDLE KeyHandle;
|
|
|
|
NTSTATUS Status;
|
|
|
|
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
|
|
&KeyName,
|
|
|
|
OBJ_CASE_INSENSITIVE,
|
|
|
|
NULL,
|
|
|
|
NULL);
|
|
|
|
Status = NtOpenKey(&KeyHandle,
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
&ObjectAttributes);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ValueBuffer[0] = Drive;
|
|
|
|
|
|
|
|
NtSetValueKey(KeyHandle,
|
|
|
|
&ValueName,
|
|
|
|
0,
|
|
|
|
REG_MULTI_SZ,
|
|
|
|
(PVOID)&ValueBuffer,
|
|
|
|
sizeof(ValueBuffer));
|
|
|
|
|
|
|
|
NtClose(KeyHandle);
|
|
|
|
}
|
|
|
|
|
2003-03-08 19:29:09 +00:00
|
|
|
/* EOF */
|