xref: /reactos/base/applications/regedit/txtproc.c (revision 14d3b53c)
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