/* * Registry Functions * * Copyright 1996 Marcus Meissner * Copyright 1998 Matthew Becker * Copyright 1999 Sylvain St-Germain * * December 21, 1997 - Kevin Cozens * Fixed bugs in the _w95_loadreg() function. Added extra information * regarding the format of the Windows '95 registry files. * * NOTES * When changing this file, please re-run the regtest program to ensure * the conditions are handled properly. * * TODO * Security access * Option handling * Time for RegEnumKey*, RegQueryInfoKey* */ #include "config.h" #include #include #include #include #include #ifdef HAVE_SYS_ERRNO_H #include #endif #include #include #include #include #include #include "windef.h" #include "winbase.h" #include "wine/winbase16.h" #include "wine/winestring.h" #include "winerror.h" #include "file.h" #include "heap.h" #include "debugtools.h" #include "xmalloc.h" #include "options.h" #include "winreg.h" #include "winversion.h" #include "server.h" #include "services.h" DEFAULT_DEBUG_CHANNEL(reg) static void REGISTRY_Init(void); /* FIXME: following defines should be configured global ... */ /* NOTE: do not append a /. linux' mkdir() WILL FAIL if you do that */ #define WINE_PREFIX "/.wine" #define SAVE_USERS_DEFAULT ETCDIR"/wine.userreg" #define SAVE_LOCAL_MACHINE_DEFAULT ETCDIR"/wine.systemreg" /* relative in ~user/.wine/ : */ #define SAVE_CURRENT_USER "user.reg" #define SAVE_LOCAL_USERS_DEFAULT "wine.userreg" #define SAVE_LOCAL_MACHINE "system.reg" #define KEY_REGISTRY "Software\\The WINE team\\WINE\\Registry" #define VAL_SAVEUPDATED "SaveOnlyUpdatedKeys" /* what valuetypes do we need to convert? */ #define UNICONVMASK ((1<0xff * ( "=\\\t" escaped in \uXXXX form.) * type,lastmodified: * int * * FIXME: doesn't save 'class' (what does it mean anyway?), nor flags. * * [HKEY_CURRENT_USER\\Software\\The WINE team\\WINE\\Registry] * SaveOnlyUpdatedKeys=yes */ /* Same as RegSaveKey but with Unix pathnames */ static void save_key( HKEY hkey, const char *filename ) { struct save_registry_request *req = get_req_buffer(); int count = 0; DWORD ret; HANDLE handle; char *p; char *name = HeapAlloc( GetProcessHeap(), 0, strlen(filename) + 20 ); if (!name) return; strcpy( name, filename ); if ((p = strrchr( name, '/' ))) p++; else p = name; for (;;) { sprintf( p, "reg%04x.tmp", count++ ); handle = FILE_CreateFile( name, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, -1 ); if (handle != INVALID_HANDLE_VALUE) break; if ((ret = GetLastError()) != ERROR_FILE_EXISTS) break; } if (handle != INVALID_HANDLE_VALUE) { req->hkey = hkey; req->file = handle; ret = server_call_noerr( REQ_SAVE_REGISTRY ); CloseHandle( handle ); if (ret) unlink( name ); else if (rename( name, filename ) == -1) { ERR( "Failed to move %s to %s: ", name, filename ); perror( "rename" ); unlink( name ); } } HeapFree( GetProcessHeap(), 0, name ); } /****************************************************************************** * SHELL_SaveRegistryBranch [Internal] * * Saves main registry branch specified by hkey. */ static void SHELL_SaveRegistryBranch(HKEY hkey) { char *fn, *home; /* Find out what to save to, get from config file */ BOOL writeToHome = PROFILE_GetWineIniBool("registry","WritetoHomeRegistries",1); BOOL writeToAlt = PROFILE_GetWineIniBool("registry","WritetoAltRegistries",1); /* FIXME: does this check apply to all keys written below ? */ if (!(home = getenv( "HOME" ))) ERR_(reg)("Failed to get homedirectory of UID %ld.\n",(long) getuid()); /* HKEY_LOCAL_MACHINE contains the HKEY_CLASSES_ROOT branch */ if (hkey == HKEY_CLASSES_ROOT) hkey = HKEY_LOCAL_MACHINE; switch (hkey) { case HKEY_CURRENT_USER: fn = xmalloc( MAX_PATHNAME_LEN ); if (writeToAlt && PROFILE_GetWineIniString( "registry", "AltCurrentUserFile", "", fn, MAX_PATHNAME_LEN - 1)) save_key( HKEY_CURRENT_USER, fn ); free (fn); if (home && writeToHome) { fn=(char*)xmalloc( strlen(home) + strlen(WINE_PREFIX) + strlen(SAVE_CURRENT_USER) + 2 ); strcpy(fn,home); strcat(fn,WINE_PREFIX); /* create the directory. don't care about errorcodes. */ mkdir(fn,0755); /* drwxr-xr-x */ strcat(fn,"/"SAVE_CURRENT_USER); save_key( HKEY_CURRENT_USER, fn ); free(fn); } break; case HKEY_LOCAL_MACHINE: /* Try first saving according to the defined location in .winerc */ fn = xmalloc ( MAX_PATHNAME_LEN); if (writeToAlt && PROFILE_GetWineIniString( "Registry", "AltLocalMachineFile", "", fn, MAX_PATHNAME_LEN - 1)) save_key( HKEY_LOCAL_MACHINE, fn ); free (fn); if (home && writeToHome) { fn=(char*)xmalloc( strlen(home) + strlen(WINE_PREFIX) + strlen(SAVE_LOCAL_MACHINE) + 2); strcpy(fn,home); strcat(fn,WINE_PREFIX"/"SAVE_LOCAL_MACHINE); save_key( HKEY_LOCAL_MACHINE, fn ); free(fn); } break; case HKEY_USERS: fn = xmalloc( MAX_PATHNAME_LEN ); if (writeToAlt && PROFILE_GetWineIniString( "Registry", "AltUserFile", "", fn, MAX_PATHNAME_LEN - 1)) save_key( HKEY_USERS, fn ); free (fn); if (home && writeToHome) { fn=(char*)xmalloc( strlen(home) + strlen(WINE_PREFIX) + strlen(SAVE_LOCAL_USERS_DEFAULT) + 2); strcpy(fn,home); strcat(fn,WINE_PREFIX"/"SAVE_LOCAL_USERS_DEFAULT); save_key( HKEY_USERS, fn ); free(fn); } break; default: ERR_(reg)("unknown/invalid key handle !\n"); break; } } /****************************************************************************** * SHELL_SaveRegistry [Internal] */ void SHELL_SaveRegistry( void ) { struct set_registry_levels_request *req = get_req_buffer(); char buf[4]; HKEY hkey; int all; TRACE_(reg)("(void)\n"); all=0; if (RegOpenKeyA(HKEY_CURRENT_USER,KEY_REGISTRY,&hkey)!=ERROR_SUCCESS) { strcpy(buf,"yes"); } else { DWORD len,junk,type; len=4; if ((ERROR_SUCCESS!=RegQueryValueExA( hkey, VAL_SAVEUPDATED, &junk, &type, buf, &len)) || (type!=REG_SZ)) { strcpy(buf,"yes"); } RegCloseKey(hkey); } if (lstrcmpiA(buf,"yes")) all = 1; /* set saving level (0 for saving everything, 1 for saving only modified keys) */ req->current = 1; req->saving = !all; req->version = PROFILE_GetWineIniBool( "registry", "UseNewFormat", 0 ) ? 2 : 1; server_call( REQ_SET_REGISTRY_LEVELS ); SHELL_SaveRegistryBranch(HKEY_CURRENT_USER); SHELL_SaveRegistryBranch(HKEY_LOCAL_MACHINE); SHELL_SaveRegistryBranch(HKEY_USERS); } /* Periodic save callback */ static void CALLBACK periodic_save( ULONG_PTR dummy ) { SHELL_SaveRegistry(); } /************************ LOAD Registry Function ****************************/ /****************************************************************************** * _find_or_add_key [Internal] */ static inline HKEY _find_or_add_key( HKEY hkey, LPWSTR keyname ) { HKEY subkey; if (RegCreateKeyW( hkey, keyname, &subkey ) != ERROR_SUCCESS) subkey = 0; if (keyname) free( keyname ); return subkey; } /****************************************************************************** * _find_or_add_value [Internal] */ static void _find_or_add_value( HKEY hkey, LPWSTR name, DWORD type, LPBYTE data, DWORD len ) { RegSetValueExW( hkey, name, 0, type, data, len ); if (name) free( name ); if (data) free( data ); } /****************************************************************************** * _wine_read_line [Internal] * * reads a line including dynamically enlarging the readbuffer and throwing * away comments */ static int _wine_read_line( FILE *F, char **buf, int *len ) { char *s,*curread; int mylen,curoff; curread = *buf; mylen = *len; **buf = '\0'; while (1) { while (1) { s=fgets(curread,mylen,F); if (s==NULL) return 0; /* EOF */ if (NULL==(s=strchr(curread,'\n'))) { /* buffer wasn't large enough */ curoff = strlen(*buf); *buf = xrealloc(*buf,*len*2); curread = *buf + curoff; mylen = *len; /* we filled up the buffer and * got new '*len' bytes to fill */ *len = *len * 2; } else { *s='\0'; break; } } /* throw away comments */ if (**buf=='#' || **buf==';') { curread = *buf; mylen = *len; continue; } if (s) /* got end of line */ break; } return 1; } /****************************************************************************** * _wine_read_USTRING [Internal] * * converts a char* into a UNICODE string (up to a special char) * and returns the position exactly after that string */ static char* _wine_read_USTRING( char *buf, LPWSTR *str ) { char *s; LPWSTR ws; /* read up to "=" or "\0" or "\n" */ s = buf; *str = (LPWSTR)xmalloc(2*strlen(buf)+2); ws = *str; while (*s && (*s!='\n') && (*s!='=')) { if (*s!='\\') *ws++=*((unsigned char*)s++); else { s++; if (!*s) { /* Dangling \ ... may only happen if a registry * write was short. FIXME: What do to? */ break; } if (*s=='\\') { *ws++='\\'; s++; continue; } if (*s!='u') { WARN_(reg)("Non unicode escape sequence \\%c found in |%s|\n",*s,buf); *ws++='\\'; *ws++=*s++; } else { char xbuf[5]; int wc; s++; memcpy(xbuf,s,4);xbuf[4]='\0'; if (!sscanf(xbuf,"%x",&wc)) WARN_(reg)("Strange escape sequence %s found in |%s|\n",xbuf,buf); s+=4; *ws++ =(unsigned short)wc; } } } *ws = 0; return s; } /****************************************************************************** * _wine_loadsubkey [Internal] * * NOTES * It seems like this is returning a boolean. Should it? * * RETURNS * Success: 1 * Failure: 0 */ static int _wine_loadsubkey( FILE *F, HKEY hkey, int level, char **buf, int *buflen ) { HKEY subkey; int i; char *s; LPWSTR name; TRACE_(reg)("(%p,%x,%d,%s,%d)\n", F, hkey, level, debugstr_a(*buf), *buflen); /* Good. We already got a line here ... so parse it */ subkey = 0; while (1) { i=0;s=*buf; while (*s=='\t') { s++; i++; } if (i>level) { if (!subkey) { WARN_(reg)("Got a subhierarchy without resp. key?\n"); return 0; } if (!_wine_loadsubkey(F,subkey,level+1,buf,buflen)) if (!_wine_read_line(F,buf,buflen)) goto done; continue; } /* let the caller handle this line */ if (i='0' && *s<='9') data[i]=(*s-'0')<<4; if (*s>='a' && *s<='f') data[i]=(*s-'a'+'\xa')<<4; if (*s>='A' && *s<='F') data[i]=(*s-'A'+'\xa')<<4; s++; if (*s>='0' && *s<='9') data[i]|=*s-'0'; if (*s>='a' && *s<='f') data[i]|=*s-'a'+'\xa'; if (*s>='A' && *s<='F') data[i]|=*s-'A'+'\xa'; s++; } } _find_or_add_value(hkey,name,type,data,len); } } /* read the next line */ if (!_wine_read_line(F,buf,buflen)) goto done; } done: if (subkey) RegCloseKey( subkey ); return 1; } /****************************************************************************** * _wine_loadsubreg [Internal] */ static int _wine_loadsubreg( FILE *F, HKEY hkey, const char *fn ) { int ver; char *buf; int buflen; buf=xmalloc(10);buflen=10; if (!_wine_read_line(F,&buf,&buflen)) { free(buf); return 0; } if (!sscanf(buf,"WINE REGISTRY Version %d",&ver)) { free(buf); return 0; } if (ver!=REGISTRY_SAVE_VERSION) { if (ver == 2) /* new version */ { HANDLE file; if ((file = FILE_CreateFile( fn, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, -1 )) != INVALID_HANDLE_VALUE) { struct load_registry_request *req = get_req_buffer(); req->hkey = hkey; req->file = file; req->name[0] = 0; server_call( REQ_LOAD_REGISTRY ); CloseHandle( file ); } free( buf ); return 1; } else { TRACE_(reg)("Old format (%d) registry found, ignoring it. (buf was %s).\n",ver,buf); free(buf); return 0; } } if (!_wine_read_line(F,&buf,&buflen)) { free(buf); return 0; } if (!_wine_loadsubkey(F,hkey,0,&buf,&buflen)) { free(buf); return 0; } free(buf); return 1; } /****************************************************************************** * _wine_loadreg [Internal] */ static void _wine_loadreg( HKEY hkey, char *fn ) { FILE *F; TRACE_(reg)("(%x,%s)\n",hkey,debugstr_a(fn)); F = fopen(fn,"rb"); if (F==NULL) { WARN_(reg)("Couldn't open %s for reading: %s\n",fn,strerror(errno) ); return; } _wine_loadsubreg(F,hkey,fn); fclose(F); } /****************************************************************************** * _flush_registry [Internal] * * This function allow to flush section of the internal registry. It is mainly * implements to fix a problem with the global HKU and the local HKU. * Those two files are read to build the HKU\.Default branch to finaly copy * this branch onto HKCU hive, once this is done, if we keep the HKU hive as is, * all the global HKU are saved onto the user's personal version of HKU hive. * which is bad... */ static void _flush_registry( HKEY hkey ) { WCHAR name[MAX_PATH]; for (;;) { HKEY subkey; /* FIXME: we assume that deleting a key will move the other ones up, */ /* so that we can always use index 0 until there are no more keys */ if (RegEnumKeyW( hkey, 0, name, sizeof(name) ) != ERROR_SUCCESS) break; if (RegOpenKeyW( hkey, name, &subkey ) != ERROR_SUCCESS) break; _flush_registry( subkey ); if (RegDeleteKeyW( subkey, NULL ) != ERROR_SUCCESS) break; RegCloseKey( subkey ); } } /****************************************************************************** * _copy_registry [Internal] */ static void _copy_registry( HKEY from, HKEY to ) { int index; HKEY subkey; FILETIME ft; DWORD type, name_len, len; static WCHAR name[MAX_PATH]; static BYTE data[2048]; /* copy values */ index = 0; for (;;) { len = sizeof(data); name_len = sizeof(name); if (RegEnumValueW( from, index++, name, &name_len, NULL, &type, data, &len ) != ERROR_SUCCESS) break; RegSetValueExW( to, name, 0, type, (LPCWSTR)data, len ); } /* copy subkeys */ index = 0; for (;;) { name_len = sizeof(name); if (RegEnumKeyExW( from, index++, name, &name_len, NULL, NULL, 0, &ft ) != ERROR_SUCCESS) break; if (RegOpenKeyW( from, name, &subkey ) == ERROR_SUCCESS) { HKEY newsub; if (RegCreateKeyW( to, name, &newsub ) == ERROR_SUCCESS) { _copy_registry( subkey, newsub ); RegCloseKey( newsub ); } RegCloseKey( subkey ); } } } /* WINDOWS 95 REGISTRY LOADER */ /* * Structure of a win95 registry database. * main header: * 0 : "CREG" - magic * 4 : DWORD version * 8 : DWORD offset_of_RGDB_part * 0C..0F: ? (someone fill in please) * 10: WORD number of RGDB blocks * 12: WORD ? * 14: WORD always 0000? * 16: WORD always 0001? * 18..1F: ? (someone fill in please) * * 20: RGKN_section: * header: * 0 : "RGKN" - magic * 4 : DWORD offset to first RGDB section * 8 : DWORD offset to the root record * C..0x1B: ? (fill in) * 0x20 ... offset_of_RGDB_part: Disk Key Entry structures * * Disk Key Entry Structure: * 00: DWORD - Free entry indicator(?) * 04: DWORD - Hash = sum of bytes of keyname * 08: DWORD - Root key indicator? unknown, but usually 0xFFFFFFFF on win95 systems * 0C: DWORD - disk address of PreviousLevel Key. * 10: DWORD - disk address of Next Sublevel Key. * 14: DWORD - disk address of Next Key (on same level). * DKEP>18: WORD - Nr, Low Significant part. * 1A: WORD - Nr, High Significant part. * * The disk address always points to the nr part of the previous key entry * of the referenced key. Don't ask me why, or even if I got this correct * from staring at 1kg of hexdumps. (DKEP) * * The High significant part of the structure seems to equal the number * of the RGDB section. The low significant part is a unique ID within * that RGDB section * * There are two minor corrections to the position of that structure. * 1. If the address is xxx014 or xxx018 it will be aligned to xxx01c AND * the DKE reread from there. * 2. If the address is xxxFFx it will be aligned to (xxx+1)000. * CPS - I have not experienced the above phenomenon in my registry files * * RGDB_section: * 00: "RGDB" - magic * 04: DWORD offset to next RGDB section * 08: DWORD ? * 0C: WORD always 000d? * 0E: WORD RGDB block number * 10: DWORD ? (equals value at offset 4 - value at offset 8) * 14..1F: ? * 20.....: disk keys * * disk key: * 00: DWORD nextkeyoffset - offset to the next disk key structure * 08: WORD nrLS - low significant part of NR * 0A: WORD nrHS - high significant part of NR * 0C: DWORD bytesused - bytes used in this structure. * 10: WORD name_len - length of name in bytes. without \0 * 12: WORD nr_of_values - number of values. * 14: char name[name_len] - name string. No \0. * 14+name_len: disk values * nextkeyoffset: ... next disk key * * disk value: * 00: DWORD type - value type (hmm, could be WORD too) * 04: DWORD - unknown, usually 0 * 08: WORD namelen - length of Name. 0 means name=NULL * 0C: WORD datalen - length of Data. * 10: char name[namelen] - name, no \0 * 10+namelen: BYTE data[datalen] - data, without \0 if string * 10+namelen+datalen: next values or disk key * * Disk keys are layed out flat ... But, sometimes, nrLS and nrHS are both * 0xFFFF, which means skipping over nextkeyoffset bytes (including this * structure) and reading another RGDB_section. * repeat until end of file. * * An interesting relationship exists in RGDB_section. The value at offset * 10 equals the value at offset 4 minus the value at offset 8. I have no * idea at the moment what this means. (Kevin Cozens) * * FIXME: this description needs some serious help, yes. */ struct _w95keyvalue { unsigned long type; unsigned short datalen; char *name; unsigned char *data; unsigned long x1; int lastmodified; }; struct _w95key { char *name; int nrofvals; struct _w95keyvalue *values; struct _w95key *prevlvl; struct _w95key *nextsub; struct _w95key *next; }; struct _w95_info { char *rgknbuffer; int rgknsize; char *rgdbbuffer; int rgdbsize; int depth; int lastmodified; }; /****************************************************************************** * _w95_processKey [Internal] */ static HKEY _w95_processKey ( HKEY hkey, int nrLS, int nrMS, struct _w95_info *info ) { /* Disk Key Header structure (RGDB part) */ struct dkh { unsigned long nextkeyoff; unsigned short nrLS; unsigned short nrMS; unsigned long bytesused; unsigned short keynamelen; unsigned short values; unsigned long xx1; /* keyname */ /* disk key values or nothing */ }; /* Disk Key Value structure */ struct dkv { unsigned long type; unsigned long x1; unsigned short valnamelen; unsigned short valdatalen; /* valname, valdata */ }; struct dkh dkh; int bytesread = 0; char *rgdbdata = info->rgdbbuffer; int nbytes = info->rgdbsize; char *curdata = rgdbdata; char *end = rgdbdata + nbytes; int off_next_rgdb; char *next = rgdbdata; int nrgdb, i; HKEY subkey; do { curdata = next; if (strncmp(curdata, "RGDB", 4)) return 0; memcpy(&off_next_rgdb,curdata+4,4); next = curdata + off_next_rgdb; nrgdb = (int) *((short *)curdata + 7); } while (nrgdb != nrMS && (next < end)); /* curdata now points to the start of the right RGDB section */ curdata += 0x20; #define XREAD(whereto,len) \ if ((curdata + len) <= end) {\ memcpy(whereto,curdata,len);\ curdata+=len;\ bytesread+=len;\ } while (curdata < next) { struct dkh *xdkh = (struct dkh*)curdata; bytesread += sizeof(dkh); /* FIXME... nextkeyoff? */ if (xdkh->nrLS == nrLS) { memcpy(&dkh,xdkh,sizeof(dkh)); curdata += sizeof(dkh); break; } curdata += xdkh->nextkeyoff; }; if (dkh.nrLS != nrLS) return 0; if (nrgdb != dkh.nrMS) return 0; assert((dkh.keynamelen<2) || curdata[0]); subkey=_find_or_add_key(hkey,strcvtA2W(curdata, dkh.keynamelen)); curdata += dkh.keynamelen; for (i=0;i< dkh.values; i++) { struct dkv dkv; LPBYTE data; int len; LPWSTR name; XREAD(&dkv,sizeof(dkv)); name = strcvtA2W(curdata, dkv.valnamelen); curdata += dkv.valnamelen; if ((1 << dkv.type) & UNICONVMASK) { data = (LPBYTE) strcvtA2W(curdata, dkv.valdatalen); len = 2*(dkv.valdatalen + 1); } else { /* I don't think we want to NULL terminate all data */ data = xmalloc(dkv.valdatalen); memcpy (data, curdata, dkv.valdatalen); len = dkv.valdatalen; } curdata += dkv.valdatalen; _find_or_add_value( subkey, name, dkv.type, data, len ); } return subkey; } /****************************************************************************** * _w95_walkrgkn [Internal] */ static void _w95_walkrgkn( HKEY prevkey, char *off, struct _w95_info *info ) { /* Disk Key Entry structure (RGKN part) */ struct dke { unsigned long x1; unsigned long x2; unsigned long x3;/*usually 0xFFFFFFFF */ unsigned long prevlvl; unsigned long nextsub; unsigned long next; unsigned short nrLS; unsigned short nrMS; } *dke = (struct dke *)off; HKEY subkey; if (dke == NULL) { dke = (struct dke *) ((char *)info->rgknbuffer); } subkey = _w95_processKey(prevkey, dke->nrLS, dke->nrMS, info); if (dke->nextsub != -1 && ((dke->nextsub - 0x20) < info->rgknsize) && (dke->nextsub > 0x20)) { _w95_walkrgkn(subkey ? subkey : prevkey, /* XXX <-- This is a hack*/ info->rgknbuffer + dke->nextsub - 0x20, info); } if (subkey) RegCloseKey( subkey ); if (dke->next != -1 && ((dke->next - 0x20) < info->rgknsize) && (dke->next > 0x20)) { _w95_walkrgkn(prevkey, info->rgknbuffer + dke->next - 0x20, info); } } /****************************************************************************** * _w95_loadreg [Internal] */ static void _w95_loadreg( char* fn, HKEY hkey ) { HFILE hfd; char magic[5]; unsigned long where,version,rgdbsection,end; struct _w95_info info; OFSTRUCT ofs; BY_HANDLE_FILE_INFORMATION hfdinfo; TRACE_(reg)("Loading Win95 registry database '%s'\n",fn); hfd=OpenFile(fn,&ofs,OF_READ); if (hfd==HFILE_ERROR) return; magic[4]=0; if (4!=_lread(hfd,magic,4)) return; if (strcmp(magic,"CREG")) { WARN_(reg)("%s is not a w95 registry.\n",fn); return; } if (4!=_lread(hfd,&version,4)) return; if (4!=_lread(hfd,&rgdbsection,4)) return; if (-1==_llseek(hfd,0x20,SEEK_SET)) return; if (4!=_lread(hfd,magic,4)) return; if (strcmp(magic,"RGKN")) { WARN_(reg)("second IFF header not RGKN, but %s\n", magic); return; } /* STEP 1: Keylink structures */ if (-1==_llseek(hfd,0x40,SEEK_SET)) return; where = 0x40; end = rgdbsection; info.rgknsize = end - where; info.rgknbuffer = (char*)xmalloc(info.rgknsize); if (info.rgknsize != _lread(hfd,info.rgknbuffer,info.rgknsize)) return; if (!GetFileInformationByHandle(hfd,&hfdinfo)) return; end = hfdinfo.nFileSizeLow; info.lastmodified = DOSFS_FileTimeToUnixTime(&hfdinfo.ftLastWriteTime,NULL); if (-1==_llseek(hfd,rgdbsection,SEEK_SET)) return; info.rgdbbuffer = (char*)xmalloc(end-rgdbsection); info.rgdbsize = end - rgdbsection; if (info.rgdbsize !=_lread(hfd,info.rgdbbuffer,info.rgdbsize)) return; _lclose(hfd); _w95_walkrgkn(hkey, NULL, &info); free (info.rgdbbuffer); free (info.rgknbuffer); } /* WINDOWS 31 REGISTRY LOADER, supplied by Tor Sjøwall, tor@sn.no */ /* reghack - windows 3.11 registry data format demo program. The reg.dat file has 3 parts, a header, a table of 8-byte entries that is a combined hash table and tree description, and finally a text table. The header is obvious from the struct header. The taboff1 and taboff2 fields are always 0x20, and their usage is unknown. The 8-byte entry table has various entry types. tabent[0] is a root index. The second word has the index of the root of the directory. tabent[1..hashsize] is a hash table. The first word in the hash entry is the index of the key/value that has that hash. Data with the same hash value are on a circular list. The other three words in the hash entry are always zero. tabent[hashsize..tabcnt] is the tree structure. There are two kinds of entry: dirent and keyent/valent. They are identified by context. tabent[freeidx] is the first free entry. The first word in a free entry is the index of the next free entry. The last has 0 as a link. The other three words in the free list are probably irrelevant. Entries in text table are preceeded by a word at offset-2. This word has the value (2*index)+1, where index is the referring keyent/valent entry in the table. I have no suggestion for the 2* and the +1. Following the word, there are N bytes of data, as per the keyent/valent entry length. The offset of the keyent/valent entry is from the start of the text table to the first data byte. This information is not available from Microsoft. The data format is deduced from the reg.dat file by me. Mistakes may have been made. I claim no rights and give no guarantees for this program. Tor Sjøwall, tor@sn.no */ /* reg.dat header format */ struct _w31_header { char cookie[8]; /* 'SHCC3.10' */ unsigned long taboff1; /* offset of hash table (??) = 0x20 */ unsigned long taboff2; /* offset of index table (??) = 0x20 */ unsigned long tabcnt; /* number of entries in index table */ unsigned long textoff; /* offset of text part */ unsigned long textsize; /* byte size of text part */ unsigned short hashsize; /* hash size */ unsigned short freeidx; /* free index */ }; /* generic format of table entries */ struct _w31_tabent { unsigned short w0, w1, w2, w3; }; /* directory tabent: */ struct _w31_dirent { unsigned short sibling_idx; /* table index of sibling dirent */ unsigned short child_idx; /* table index of child dirent */ unsigned short key_idx; /* table index of key keyent */ unsigned short value_idx; /* table index of value valent */ }; /* key tabent: */ struct _w31_keyent { unsigned short hash_idx; /* hash chain index for string */ unsigned short refcnt; /* reference count */ unsigned short length; /* length of string */ unsigned short string_off; /* offset of string in text table */ }; /* value tabent: */ struct _w31_valent { unsigned short hash_idx; /* hash chain index for string */ unsigned short refcnt; /* reference count */ unsigned short length; /* length of string */ unsigned short string_off; /* offset of string in text table */ }; /* recursive helper function to display a directory tree */ void __w31_dumptree( unsigned short idx, unsigned char *txt, struct _w31_tabent *tab, struct _w31_header *head, HKEY hkey, time_t lastmodified, int level ) { struct _w31_dirent *dir; struct _w31_keyent *key; struct _w31_valent *val; HKEY subkey = 0; static char tail[400]; while (idx!=0) { dir=(struct _w31_dirent*)&tab[idx]; if (dir->key_idx) { key = (struct _w31_keyent*)&tab[dir->key_idx]; memcpy(tail,&txt[key->string_off],key->length); tail[key->length]='\0'; /* all toplevel entries AND the entries in the * toplevel subdirectory belong to \SOFTWARE\Classes */ if (!level && !lstrcmpA(tail,".classes")) { __w31_dumptree(dir->child_idx,txt,tab,head,hkey,lastmodified,level+1); idx=dir->sibling_idx; continue; } if (subkey) RegCloseKey( subkey ); if (RegCreateKeyA( hkey, tail, &subkey ) != ERROR_SUCCESS) subkey = 0; /* only add if leaf node or valued node */ if (dir->value_idx!=0||dir->child_idx==0) { if (dir->value_idx) { val=(struct _w31_valent*)&tab[dir->value_idx]; memcpy(tail,&txt[val->string_off],val->length); tail[val->length]='\0'; RegSetValueA( subkey, NULL, REG_SZ, tail, 0 ); } } } else { TRACE_(reg)("strange: no directory key name, idx=%04x\n", idx); } __w31_dumptree(dir->child_idx,txt,tab,head,subkey,lastmodified,level+1); idx=dir->sibling_idx; } if (subkey) RegCloseKey( subkey ); } /****************************************************************************** * _w31_loadreg [Internal] */ void _w31_loadreg(void) { HFILE hf; struct _w31_header head; struct _w31_tabent *tab; unsigned char *txt; int len; OFSTRUCT ofs; BY_HANDLE_FILE_INFORMATION hfinfo; time_t lastmodified; TRACE_(reg)("(void)\n"); hf = OpenFile("reg.dat",&ofs,OF_READ); if (hf==HFILE_ERROR) return; /* read & dump header */ if (sizeof(head)!=_lread(hf,&head,sizeof(head))) { ERR_(reg)("reg.dat is too short.\n"); _lclose(hf); return; } if (memcmp(head.cookie, "SHCC3.10", sizeof(head.cookie))!=0) { ERR_(reg)("reg.dat has bad signature.\n"); _lclose(hf); return; } len = head.tabcnt * sizeof(struct _w31_tabent); /* read and dump index table */ tab = xmalloc(len); if (len!=_lread(hf,tab,len)) { ERR_(reg)("couldn't read %d bytes.\n",len); free(tab); _lclose(hf); return; } /* read text */ txt = xmalloc(head.textsize); if (-1==_llseek(hf,head.textoff,SEEK_SET)) { ERR_(reg)("couldn't seek to textblock.\n"); free(tab); free(txt); _lclose(hf); return; } if (head.textsize!=_lread(hf,txt,head.textsize)) { ERR_(reg)("textblock too short (%d instead of %ld).\n",len,head.textsize); free(tab); free(txt); _lclose(hf); return; } if (!GetFileInformationByHandle(hf,&hfinfo)) { ERR_(reg)("GetFileInformationByHandle failed?.\n"); free(tab); free(txt); _lclose(hf); return; } lastmodified = DOSFS_FileTimeToUnixTime(&hfinfo.ftLastWriteTime,NULL); __w31_dumptree(tab[0].w1,txt,tab,&head,HKEY_CLASSES_ROOT,lastmodified,0); free(tab); free(txt); _lclose(hf); return; } /********************************************************************************** * SHELL_LoadRegistry [Internal] */ void SHELL_LoadRegistry( void ) { struct set_registry_levels_request *req = get_req_buffer(); int save_timeout; char *fn, *home; HKEY hkey; TRACE_(reg)("(void)\n"); REGISTRY_Init(); /* set level to 0 for loading system files */ req->current = 0; req->saving = 0; req->version = 1; server_call( REQ_SET_REGISTRY_LEVELS ); if (PROFILE_GetWineIniBool ("registry", "LoadWindowsRegistryFiles", 1)) { /* Load windows 3.1 entries */ _w31_loadreg(); /* Load windows 95 entries */ _w95_loadreg("C:\\system.1st", HKEY_LOCAL_MACHINE); _w95_loadreg("system.dat", HKEY_LOCAL_MACHINE); _w95_loadreg("user.dat", HKEY_USERS); } if (PROFILE_GetWineIniBool ("registry","LoadGlobalRegistryFiles", 1)) { /* * Load the global HKU hive directly from sysconfdir */ _wine_loadreg( HKEY_USERS, SAVE_USERS_DEFAULT ); /* * Load the global machine defaults directly form sysconfdir */ _wine_loadreg( HKEY_LOCAL_MACHINE, SAVE_LOCAL_MACHINE_DEFAULT ); } /* set level to 1 for loading user files */ req->current = 1; req->saving = 0; req->version = 1; server_call( REQ_SET_REGISTRY_LEVELS ); /* * Load the user saved registries */ if (!(home = getenv( "HOME" ))) WARN_(reg)("Failed to get homedirectory of UID %ld.\n",(long) getuid()); else if (PROFILE_GetWineIniBool("registry", "LoadHomeRegistryFiles", 1)) { /* * Load user's personal versions of global HKU/.Default keys */ fn=(char*)xmalloc( strlen(home)+ strlen(WINE_PREFIX) + strlen(SAVE_LOCAL_USERS_DEFAULT)+2); strcpy(fn, home); strcat(fn, WINE_PREFIX"/"SAVE_LOCAL_USERS_DEFAULT); _wine_loadreg( HKEY_USERS, fn ); free(fn); fn=(char*)xmalloc( strlen(home) + strlen(WINE_PREFIX) + strlen(SAVE_CURRENT_USER)+2); strcpy(fn, home); strcat(fn, WINE_PREFIX"/"SAVE_CURRENT_USER); _wine_loadreg( HKEY_CURRENT_USER, fn ); free(fn); /* * Load HKLM, attempt to get the registry location from the config * file first, if exist, load and keep going. */ fn=(char*)xmalloc( strlen(home)+ strlen(WINE_PREFIX)+ strlen(SAVE_LOCAL_MACHINE)+2); strcpy(fn,home); strcat(fn,WINE_PREFIX"/"SAVE_LOCAL_MACHINE); _wine_loadreg( HKEY_LOCAL_MACHINE, fn ); free(fn); } /* * Load HKCU, get the registry location from the config * file, if exist, load and keep going. */ if (PROFILE_GetWineIniBool ( "registry", "LoadAltRegistryFiles", 1)) { fn = xmalloc( MAX_PATHNAME_LEN ); if ( PROFILE_GetWineIniString( "registry", "AltCurrentUserFile", "", fn, MAX_PATHNAME_LEN - 1)) { _wine_loadreg( HKEY_CURRENT_USER, fn ); } free (fn); /* * Load HKU, get the registry location from the config * file, if exist, load and keep going. */ fn = xmalloc ( MAX_PATHNAME_LEN ); if ( PROFILE_GetWineIniString ( "registry", "AltUserFile", "", fn, MAX_PATHNAME_LEN - 1)) { _wine_loadreg( HKEY_USERS, fn ); } free (fn); /* * Load HKLM, get the registry location from the config * file, if exist, load and keep going. */ fn = xmalloc ( MAX_PATHNAME_LEN ); if (PROFILE_GetWineIniString ( "registry", "AltLocalMachineFile", "", fn, MAX_PATHNAME_LEN - 1)) { _wine_loadreg( HKEY_LOCAL_MACHINE, fn ); } free (fn); } /* * Obtain the handle of the HKU\.Default key. * in order to copy HKU\.Default\* onto HKEY_CURRENT_USER */ if (RegCreateKeyA(HKEY_USERS,".Default",&hkey) != ERROR_SUCCESS) WARN_(reg)("Could not create global user default key\n"); else _copy_registry( hkey, HKEY_CURRENT_USER ); RegCloseKey(hkey); /* * Since HKU is built from the global HKU and the local user HKU file we must * flush the HKU tree we have built at this point otherwise the part brought * in from the global HKU is saved into the local HKU. To avoid this * useless dupplication of HKU keys we reread the local HKU key. */ /* Allways flush the HKU hive and reload it only with user's personal HKU */ _flush_registry( HKEY_USERS ); /* Reload user's local HKU hive */ if (home && PROFILE_GetWineIniBool ("registry","LoadHomeRegistryFiles",1)) { fn=(char*)xmalloc( strlen(home) + strlen(WINE_PREFIX) + strlen(SAVE_LOCAL_USERS_DEFAULT) + 2); strcpy(fn,home); strcat(fn,WINE_PREFIX"/"SAVE_LOCAL_USERS_DEFAULT); _wine_loadreg( HKEY_USERS, fn ); free(fn); } /* * Make sure the update mode is there */ if (ERROR_SUCCESS==RegCreateKey16(HKEY_CURRENT_USER,KEY_REGISTRY,&hkey)) { DWORD junk,type,len; char data[5]; len=4; if (( RegQueryValueExA( hkey, VAL_SAVEUPDATED, &junk, &type, data, &len) != ERROR_SUCCESS) || (type != REG_SZ)) { RegSetValueExA(hkey,VAL_SAVEUPDATED,0,REG_SZ,"yes",4); } RegCloseKey(hkey); } if ((save_timeout = PROFILE_GetWineIniInt( "registry", "PeriodicSave", 0 ))) { SERVICE_AddTimer( save_timeout * 1000000, periodic_save, 0 ); } } /********************* API FUNCTIONS ***************************************/ /****************************************************************************** * RegFlushKey [KERNEL.227] [ADVAPI32.143] * Immediately writes key to registry. * Only returns after data has been written to disk. * * FIXME: does it really wait until data is written ? * * PARAMS * hkey [I] Handle of key to write * * RETURNS * Success: ERROR_SUCCESS * Failure: Error code */ DWORD WINAPI RegFlushKey( HKEY hkey ) { FIXME( "(%x): stub\n", hkey ); return ERROR_SUCCESS; } /****************************************************************************** * RegConnectRegistry32W [ADVAPI32.128] * * PARAMS * lpMachineName [I] Address of name of remote computer * hHey [I] Predefined registry handle * phkResult [I] Address of buffer for remote registry handle */ LONG WINAPI RegConnectRegistryW( LPCWSTR lpMachineName, HKEY hKey, LPHKEY phkResult ) { TRACE_(reg)("(%s,%x,%p): stub\n",debugstr_w(lpMachineName),hKey,phkResult); if (!lpMachineName || !*lpMachineName) { /* Use the local machine name */ return RegOpenKey16( hKey, "", phkResult ); } FIXME_(reg)("Cannot connect to %s\n",debugstr_w(lpMachineName)); return ERROR_BAD_NETPATH; } /****************************************************************************** * RegConnectRegistry32A [ADVAPI32.127] */ LONG WINAPI RegConnectRegistryA( LPCSTR machine, HKEY hkey, LPHKEY reskey ) { DWORD ret; LPWSTR machineW = strdupA2W(machine); ret = RegConnectRegistryW( machineW, hkey, reskey ); free(machineW); return ret; } /****************************************************************************** * RegGetKeySecurity [ADVAPI32.144] * Retrieves a copy of security descriptor protecting the registry key * * PARAMS * hkey [I] Open handle of key to set * SecurityInformation [I] Descriptor contents * pSecurityDescriptor [O] Address of descriptor for key * lpcbSecurityDescriptor [I/O] Address of size of buffer and description * * RETURNS * Success: ERROR_SUCCESS * Failure: Error code */ LONG WINAPI RegGetKeySecurity( HKEY hkey, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR pSecurityDescriptor, LPDWORD lpcbSecurityDescriptor ) { TRACE_(reg)("(%x,%ld,%p,%ld)\n",hkey,SecurityInformation,pSecurityDescriptor, lpcbSecurityDescriptor?*lpcbSecurityDescriptor:0); /* FIXME: Check for valid SecurityInformation values */ if (*lpcbSecurityDescriptor < sizeof(SECURITY_DESCRIPTOR)) return ERROR_INSUFFICIENT_BUFFER; FIXME_(reg)("(%x,%ld,%p,%ld): stub\n",hkey,SecurityInformation, pSecurityDescriptor,lpcbSecurityDescriptor?*lpcbSecurityDescriptor:0); return ERROR_SUCCESS; } /****************************************************************************** * RegNotifyChangeKeyValue [ADVAPI32.???] * * PARAMS * hkey [I] Handle of key to watch * fWatchSubTree [I] Flag for subkey notification * fdwNotifyFilter [I] Changes to be reported * hEvent [I] Handle of signaled event * fAsync [I] Flag for asynchronous reporting */ LONG WINAPI RegNotifyChangeKeyValue( HKEY hkey, BOOL fWatchSubTree, DWORD fdwNotifyFilter, HANDLE hEvent, BOOL fAsync ) { FIXME_(reg)("(%x,%i,%ld,%x,%i): stub\n",hkey,fWatchSubTree,fdwNotifyFilter, hEvent,fAsync); return ERROR_SUCCESS; } /****************************************************************************** * RegUnLoadKey32W [ADVAPI32.173] * * PARAMS * hkey [I] Handle of open key * lpSubKey [I] Address of name of subkey to unload */ LONG WINAPI RegUnLoadKeyW( HKEY hkey, LPCWSTR lpSubKey ) { FIXME_(reg)("(%x,%s): stub\n",hkey, debugstr_w(lpSubKey)); return ERROR_SUCCESS; } /****************************************************************************** * RegUnLoadKey32A [ADVAPI32.172] */ LONG WINAPI RegUnLoadKeyA( HKEY hkey, LPCSTR lpSubKey ) { LONG ret; LPWSTR lpSubKeyW = strdupA2W(lpSubKey); ret = RegUnLoadKeyW( hkey, lpSubKeyW ); if(lpSubKeyW) free(lpSubKeyW); return ret; } /****************************************************************************** * RegSetKeySecurity [ADVAPI32.167] * * PARAMS * hkey [I] Open handle of key to set * SecurityInfo [I] Descriptor contents * pSecurityDesc [I] Address of descriptor for key */ LONG WINAPI RegSetKeySecurity( HKEY hkey, SECURITY_INFORMATION SecurityInfo, PSECURITY_DESCRIPTOR pSecurityDesc ) { TRACE_(reg)("(%x,%ld,%p)\n",hkey,SecurityInfo,pSecurityDesc); /* It seems to perform this check before the hkey check */ if ((SecurityInfo & OWNER_SECURITY_INFORMATION) || (SecurityInfo & GROUP_SECURITY_INFORMATION) || (SecurityInfo & DACL_SECURITY_INFORMATION) || (SecurityInfo & SACL_SECURITY_INFORMATION)) { /* Param OK */ } else return ERROR_INVALID_PARAMETER; if (!pSecurityDesc) return ERROR_INVALID_PARAMETER; FIXME_(reg)(":(%x,%ld,%p): stub\n",hkey,SecurityInfo,pSecurityDesc); return ERROR_SUCCESS; } /****************************************************************************** * RegRestoreKey32W [ADVAPI32.164] * * PARAMS * hkey [I] Handle of key where restore begins * lpFile [I] Address of filename containing saved tree * dwFlags [I] Optional flags */ LONG WINAPI RegRestoreKeyW( HKEY hkey, LPCWSTR lpFile, DWORD dwFlags ) { TRACE_(reg)("(%x,%s,%ld)\n",hkey,debugstr_w(lpFile),dwFlags); /* It seems to do this check before the hkey check */ if (!lpFile || !*lpFile) return ERROR_INVALID_PARAMETER; FIXME_(reg)("(%x,%s,%ld): stub\n",hkey,debugstr_w(lpFile),dwFlags); /* Check for file existence */ return ERROR_SUCCESS; } /****************************************************************************** * RegRestoreKey32A [ADVAPI32.163] */ LONG WINAPI RegRestoreKeyA( HKEY hkey, LPCSTR lpFile, DWORD dwFlags ) { LONG ret; LPWSTR lpFileW = strdupA2W(lpFile); ret = RegRestoreKeyW( hkey, lpFileW, dwFlags ); if(lpFileW) free(lpFileW); return ret; } /****************************************************************************** * RegReplaceKey32W [ADVAPI32.162] * * PARAMS * hkey [I] Handle of open key * lpSubKey [I] Address of name of subkey * lpNewFile [I] Address of filename for file with new data * lpOldFile [I] Address of filename for backup file */ LONG WINAPI RegReplaceKeyW( HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpNewFile, LPCWSTR lpOldFile ) { FIXME_(reg)("(%x,%s,%s,%s): stub\n", hkey, debugstr_w(lpSubKey), debugstr_w(lpNewFile),debugstr_w(lpOldFile)); return ERROR_SUCCESS; } /****************************************************************************** * RegReplaceKey32A [ADVAPI32.161] */ LONG WINAPI RegReplaceKeyA( HKEY hkey, LPCSTR lpSubKey, LPCSTR lpNewFile, LPCSTR lpOldFile ) { LONG ret; LPWSTR lpSubKeyW = strdupA2W(lpSubKey); LPWSTR lpNewFileW = strdupA2W(lpNewFile); LPWSTR lpOldFileW = strdupA2W(lpOldFile); ret = RegReplaceKeyW( hkey, lpSubKeyW, lpNewFileW, lpOldFileW ); free(lpOldFileW); free(lpNewFileW); free(lpSubKeyW); return ret; } /* 16-bit functions */ /* 0 and 1 are valid rootkeys in win16 shell.dll and are used by * some programs. Do not remove those cases. -MM */ static inline void fix_win16_hkey( HKEY *hkey ) { if (*hkey == 0 || *hkey == 1) *hkey = HKEY_CLASSES_ROOT; } /****************************************************************************** * RegEnumKey16 [KERNEL.216] [SHELL.7] */ DWORD WINAPI RegEnumKey16( HKEY hkey, DWORD index, LPSTR name, DWORD name_len ) { fix_win16_hkey( &hkey ); return RegEnumKeyA( hkey, index, name, name_len ); } /****************************************************************************** * RegOpenKey16 [KERNEL.217] [SHELL.1] */ DWORD WINAPI RegOpenKey16( HKEY hkey, LPCSTR name, LPHKEY retkey ) { fix_win16_hkey( &hkey ); return RegOpenKeyA( hkey, name, retkey ); } /****************************************************************************** * RegCreateKey16 [KERNEL.218] [SHELL.2] */ DWORD WINAPI RegCreateKey16( HKEY hkey, LPCSTR name, LPHKEY retkey ) { fix_win16_hkey( &hkey ); return RegCreateKeyA( hkey, name, retkey ); } /****************************************************************************** * RegDeleteKey16 [KERNEL.219] [SHELL.4] */ DWORD WINAPI RegDeleteKey16( HKEY hkey, LPCSTR name ) { fix_win16_hkey( &hkey ); return RegDeleteKeyA( hkey, name ); } /****************************************************************************** * RegCloseKey16 [KERNEL.220] [SHELL.3] */ DWORD WINAPI RegCloseKey16( HKEY hkey ) { fix_win16_hkey( &hkey ); return RegCloseKey( hkey ); } /****************************************************************************** * RegSetValue16 [KERNEL.221] [SHELL.5] */ DWORD WINAPI RegSetValue16( HKEY hkey, LPCSTR name, DWORD type, LPCSTR data, DWORD count ) { fix_win16_hkey( &hkey ); return RegSetValueA( hkey, name, type, data, count ); } /****************************************************************************** * RegDeleteValue16 [KERNEL.222] */ DWORD WINAPI RegDeleteValue16( HKEY hkey, LPSTR name ) { fix_win16_hkey( &hkey ); return RegDeleteValueA( hkey, name ); } /****************************************************************************** * RegEnumValue16 [KERNEL.223] */ DWORD WINAPI RegEnumValue16( HKEY hkey, DWORD index, LPSTR value, LPDWORD val_count, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD count ) { fix_win16_hkey( &hkey ); return RegEnumValueA( hkey, index, value, val_count, reserved, type, data, count ); } /****************************************************************************** * RegQueryValue16 [KERNEL.224] [SHELL.6] * * NOTES * Is this HACK still applicable? * * HACK * The 16bit RegQueryValue doesn't handle selectorblocks anyway, so we just * mask out the high 16 bit. This (not so much incidently) hopefully fixes * Aldus FH4) */ DWORD WINAPI RegQueryValue16( HKEY hkey, LPCSTR name, LPSTR data, LPDWORD count ) { fix_win16_hkey( &hkey ); if (count) *count &= 0xffff; return RegQueryValueA( hkey, name, data, count ); } /****************************************************************************** * RegQueryValueEx16 [KERNEL.225] */ DWORD WINAPI RegQueryValueEx16( HKEY hkey, LPCSTR name, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD count ) { fix_win16_hkey( &hkey ); return RegQueryValueExA( hkey, name, reserved, type, data, count ); } /****************************************************************************** * RegSetValueEx16 [KERNEL.226] */ DWORD WINAPI RegSetValueEx16( HKEY hkey, LPCSTR name, DWORD reserved, DWORD type, CONST BYTE *data, DWORD count ) { fix_win16_hkey( &hkey ); return RegSetValueExA( hkey, name, reserved, type, data, count ); }