1 /* 2 * PROJECT: ReactOS Registry Editor 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Exporting registry data to a text file 5 * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #include "regedit.h" 9 10 static HKEY reg_class_keys[] = 11 { 12 HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CLASSES_ROOT, 13 HKEY_CURRENT_CONFIG, HKEY_CURRENT_USER, HKEY_DYN_DATA 14 }; 15 16 static LPWSTR load_str(INT id) 17 { 18 /* Use rotation buffer */ 19 static WCHAR s_asz[3][MAX_PATH]; 20 static INT s_index = 0; 21 LPWSTR psz; 22 LoadStringW(hInst, id, s_asz[s_index], MAX_PATH); 23 psz = s_asz[s_index]; 24 s_index = (s_index + 1) % _countof(s_asz); 25 return psz; 26 } 27 28 static void txt_fputs(FILE *fp, LPCWSTR str) 29 { 30 fwrite(str, lstrlenW(str) * sizeof(WCHAR), 1, fp); 31 } 32 33 static void txt_newline(FILE *fp) 34 { 35 txt_fputs(fp, L"\r\n"); 36 } 37 38 static void txt_fprintf(FILE *fp, LPCWSTR format, ...) 39 { 40 WCHAR line[1024]; 41 va_list va; 42 va_start(va, format); 43 StringCchVPrintfW(line, _countof(line), format, va); 44 txt_fputs(fp, line); 45 va_end(va); 46 } 47 48 static HKEY txt_parse_key_name(LPCWSTR key_name, WCHAR **key_path) 49 { 50 unsigned int i; 51 52 if (!key_name) return 0; 53 54 *key_path = wcschr(key_name, '\\'); 55 if (*key_path) 56 (*key_path)++; 57 58 for (i = 0; i < _countof(reg_class_keys); i++) 59 { 60 int len = lstrlenW(reg_class_namesW[i]); 61 if (!_wcsnicmp(key_name, reg_class_namesW[i], len) && 62 (key_name[len] == 0 || key_name[len] == '\\')) 63 { 64 return reg_class_keys[i]; 65 } 66 } 67 68 return 0; 69 } 70 71 static void txt_export_binary(FILE *fp, const void *data, size_t size) 72 { 73 const BYTE *pb = data; 74 for (DWORD addr = 0; addr < size; addr += 0x10) 75 { 76 txt_fprintf(fp, L"%08X ", addr); 77 for (size_t column = 0; column < 16; ++column) 78 { 79 if (addr + column >= size) 80 { 81 if (column == 8) 82 txt_fputs(fp, L" "); 83 txt_fputs(fp, L" "); 84 } 85 else 86 { 87 if (column == 8) 88 txt_fputs(fp, L" -"); 89 txt_fprintf(fp, L" %02x", (pb[addr + column] & 0xFF)); 90 } 91 } 92 txt_fputs(fp, L" "); 93 for (size_t column = 0; column < 16; ++column) 94 { 95 if (addr + column >= size) 96 { 97 break; 98 } 99 else 100 { 101 BYTE b = pb[addr + column]; 102 if (isprint(b) || IsCharAlphaNumericW(b)) 103 txt_fprintf(fp, L"%c", b); 104 else 105 txt_fputs(fp, L"."); 106 } 107 } 108 txt_newline(fp); 109 } 110 } 111 112 static void txt_export_field(FILE *fp, LPCWSTR label, LPCWSTR value) 113 { 114 txt_fprintf(fp, L"%-19s%s\r\n", label, value); 115 } 116 117 static void txt_export_multi_sz(FILE *fp, const void *data, size_t size) 118 { 119 LPCWSTR pch; 120 for (pch = data; *pch; pch += lstrlenW(pch) + 1) 121 { 122 if (pch == data) 123 txt_export_field(fp, load_str(IDS_FIELD_DATA), pch); 124 else 125 txt_export_field(fp, L"", pch); 126 } 127 } 128 129 static void txt_export_type(FILE *fp, LPCWSTR type) 130 { 131 txt_export_field(fp, load_str(IDS_FIELD_TYPE), type); 132 } 133 134 static void txt_export_name(FILE *fp, LPCWSTR name) 135 { 136 txt_export_field(fp, load_str(IDS_FIELD_NAME), name); 137 } 138 139 static void 140 txt_export_data(FILE *fp, INT i, LPCWSTR value_name, DWORD value_len, DWORD type, 141 const void *data, size_t size) 142 { 143 LPCWSTR pszType; 144 145 txt_fprintf(fp, load_str(IDS_VALUE_INDEX), i); 146 txt_newline(fp); 147 txt_export_name(fp, value_name); 148 149 switch (type) 150 { 151 case REG_SZ: 152 txt_export_type(fp, L"REG_SZ"); 153 txt_export_field(fp, load_str(IDS_FIELD_DATA), data); 154 break; 155 156 case REG_DWORD: 157 txt_export_type(fp, L"REG_DWORD"); 158 txt_fprintf(fp, L"%-19s0x%lx\r\n", load_str(IDS_FIELD_DATA), *(DWORD*)data); 159 break; 160 161 case REG_EXPAND_SZ: 162 txt_export_type(fp, L"REG_EXPAND_SZ"); 163 txt_export_field(fp, load_str(IDS_FIELD_DATA), data); 164 break; 165 166 case REG_MULTI_SZ: 167 txt_export_type(fp, L"REG_MULTI_SZ"); 168 txt_export_multi_sz(fp, data, size); 169 break; 170 171 case REG_BINARY: 172 case REG_QWORD: 173 case REG_NONE: 174 default: 175 if (type == REG_BINARY) 176 pszType = L"REG_BINARY"; 177 else if (type == REG_QWORD) 178 pszType = L"REG_QWORD"; 179 else if (type == REG_NONE) 180 pszType = L"REG_NONE"; 181 else 182 pszType = load_str(IDS_UNKNOWN); 183 184 txt_export_type(fp, pszType); 185 txt_export_field(fp, load_str(IDS_FIELD_DATA), L""); 186 txt_export_binary(fp, data, size); 187 break; 188 } 189 190 txt_newline(fp); 191 } 192 193 static WCHAR * 194 txt_build_subkey_path(LPCWSTR path, DWORD path_len, LPCWSTR subkey_name, DWORD subkey_len) 195 { 196 WCHAR *subkey_path; 197 SIZE_T cb_subkey_path = (path_len + subkey_len + 2) * sizeof(WCHAR); 198 subkey_path = malloc(cb_subkey_path); 199 StringCbPrintfW(subkey_path, cb_subkey_path, L"%s\\%s", path, subkey_name); 200 return subkey_path; 201 } 202 203 static void txt_export_key_name(FILE *fp, LPCWSTR name) 204 { 205 txt_export_field(fp, load_str(IDS_FIELD_KEY_NAME), name); 206 } 207 208 static void txt_export_class_and_last_write(FILE *fp, HKEY key) 209 { 210 WCHAR szClassName[MAX_PATH]; 211 DWORD cchClassName = _countof(szClassName); 212 FILETIME ftLastWrite, ftLocal, ftNull = { 0 }; 213 SYSTEMTIME stLastWrite; 214 WCHAR sz1[64], sz2[64]; 215 LONG error; 216 217 error = RegQueryInfoKeyW(key, szClassName, &cchClassName, NULL, NULL, NULL, NULL, NULL, NULL, 218 NULL, NULL, &ftLastWrite); 219 if (error != ERROR_SUCCESS) 220 { 221 cchClassName = 0; 222 ftLastWrite = ftNull; 223 } 224 225 szClassName[_countof(szClassName) - 1] = UNICODE_NULL; 226 227 if (cchClassName > 0) 228 txt_export_field(fp, load_str(IDS_FIELD_CLASS_NAME), szClassName); 229 else 230 txt_export_field(fp, load_str(IDS_FIELD_CLASS_NAME), load_str(IDS_NO_CLASS_NAME)); 231 232 if (memcmp(&ftLastWrite, &ftNull, sizeof(ftNull)) == 0) 233 { 234 txt_export_field(fp, load_str(IDS_FIELD_LASTWRITE), load_str(IDS_NULL_TIMESTAMP)); 235 return; 236 } 237 238 FileTimeToLocalFileTime(&ftLastWrite, &ftLocal); 239 FileTimeToSystemTime(&ftLocal, &stLastWrite); 240 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastWrite, NULL, sz1, _countof(sz1)); 241 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &stLastWrite, NULL, sz2, _countof(sz2)); 242 txt_fprintf(fp, L"%-19s%s - %s\r\n", load_str(IDS_FIELD_LASTWRITE), sz1, sz2); 243 } 244 245 static void txt_export_registry_data(FILE *fp, HKEY key, LPCWSTR path) 246 { 247 LONG rc; 248 DWORD max_value_len = MAX_PATH, value_len; 249 DWORD max_data_bytes = 2048, data_size; 250 DWORD subkey_len; 251 DWORD i, type, path_len; 252 WCHAR *value_name, *subkey_name, *subkey_path; 253 BYTE *data; 254 HKEY subkey; 255 256 txt_export_key_name(fp, path); 257 txt_export_class_and_last_write(fp, key); 258 259 value_name = malloc(max_value_len * sizeof(WCHAR)); 260 data = malloc(max_data_bytes); 261 262 i = 0; 263 for (;;) 264 { 265 value_len = max_value_len; 266 data_size = max_data_bytes; 267 rc = RegEnumValueW(key, i, value_name, &value_len, NULL, &type, data, &data_size); 268 if (rc == ERROR_SUCCESS) 269 { 270 txt_export_data(fp, i, value_name, value_len, type, data, data_size); 271 i++; 272 } 273 else if (rc == ERROR_MORE_DATA) 274 { 275 if (data_size > max_data_bytes) 276 { 277 max_data_bytes = data_size; 278 data = realloc(data, max_data_bytes); 279 } 280 else 281 { 282 max_value_len *= 2; 283 value_name = realloc(value_name, max_value_len * sizeof(WCHAR)); 284 } 285 } 286 else break; 287 } 288 289 free(data); 290 free(value_name); 291 292 subkey_name = malloc(MAX_PATH * sizeof(WCHAR)); 293 294 path_len = lstrlenW(path); 295 296 i = 0; 297 for (;;) 298 { 299 subkey_len = MAX_PATH; 300 rc = RegEnumKeyExW(key, i, subkey_name, &subkey_len, NULL, NULL, NULL, NULL); 301 if (rc == ERROR_SUCCESS) 302 { 303 if (i == 0) 304 txt_newline(fp); 305 306 subkey_path = txt_build_subkey_path(path, path_len, subkey_name, subkey_len); 307 if (!RegOpenKeyExW(key, subkey_name, 0, KEY_READ, &subkey)) 308 { 309 txt_newline(fp); 310 txt_export_registry_data(fp, subkey, subkey_path); 311 RegCloseKey(subkey); 312 } 313 free(subkey_path); 314 i++; 315 } 316 else break; 317 } 318 319 free(subkey_name); 320 } 321 322 static FILE *txt_open_export_file(LPCWSTR file_name) 323 { 324 FILE *file = _wfopen(file_name, L"wb"); 325 if (file) 326 fwrite("\xFF\xFE", 2, 1, file); 327 return file; 328 } 329 330 static HKEY txt_open_export_key(HKEY key_class, LPCWSTR subkey, LPCWSTR path) 331 { 332 HKEY key; 333 334 if (RegOpenKeyExW(key_class, subkey, 0, KEY_READ, &key) != ERROR_SUCCESS) 335 return NULL; 336 337 return key; 338 } 339 340 static BOOL txt_export_key(LPCWSTR file_name, LPCWSTR path) 341 { 342 HKEY key_class, key; 343 WCHAR *subkey; 344 FILE *fp; 345 346 if (!(key_class = txt_parse_key_name(path, &subkey))) 347 { 348 if (subkey) *(subkey - 1) = 0; 349 return FALSE; 350 } 351 352 if (!(key = txt_open_export_key(key_class, subkey, path))) 353 return FALSE; 354 355 fp = txt_open_export_file(file_name); 356 if (fp) 357 { 358 txt_export_registry_data(fp, key, path); 359 txt_newline(fp); 360 fclose(fp); 361 } 362 363 RegCloseKey(key); 364 return fp != NULL; 365 } 366 367 static BOOL txt_export_all(LPCWSTR file_name, LPCWSTR path) 368 { 369 FILE *fp; 370 int i; 371 HKEY classes[] = {HKEY_LOCAL_MACHINE, HKEY_USERS}, key; 372 WCHAR *class_name; 373 374 fp = txt_open_export_file(file_name); 375 if (!fp) 376 return FALSE; 377 378 for (i = 0; i < _countof(classes); i++) 379 { 380 if (!(key = txt_open_export_key(classes[i], NULL, path))) 381 { 382 fclose(fp); 383 return FALSE; 384 } 385 386 class_name = malloc((lstrlenW(reg_class_namesW[i]) + 1) * sizeof(WCHAR)); 387 lstrcpyW(class_name, reg_class_namesW[i]); 388 389 txt_export_registry_data(fp, classes[i], class_name); 390 391 free(class_name); 392 RegCloseKey(key); 393 } 394 395 txt_newline(fp); 396 fclose(fp); 397 398 return TRUE; 399 } 400 401 BOOL txt_export_registry_key(LPCWSTR file_name, LPCWSTR path) 402 { 403 if (path && *path) 404 return txt_export_key(file_name, path); 405 else 406 return txt_export_all(file_name, path); 407 } 408