Merge branch 'ntfs_rebase'
[reactos.git] / sdk / tools / xml2sdb / xml2sdb.cpp
1 /*
2 * PROJECT: xml2sdb
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Conversion functions from xml -> db
5 * COPYRIGHT: Copyright 2016,2017 Mark Jansen (mark.jansen@reactos.org)
6 */
7
8 #include "xml2sdb.h"
9 #include "sdbpapi.h"
10 #include "tinyxml2.h"
11 #include <time.h>
12 #include <algorithm>
13
14 using tinyxml2::XMLText;
15
16 static const GUID GUID_NULL = { 0 };
17 static const char szCompilerVersion[] = "1.6.0.0";
18
19 #if !defined(C_ASSERT)
20 #define C_ASSERT(expr) extern char (*c_assert(void)) [(expr) ? 1 : -1]
21 #endif
22
23
24 C_ASSERT(sizeof(GUID) == 16);
25 C_ASSERT(sizeof(ULONG) == 4);
26 C_ASSERT(sizeof(LARGE_INTEGER) == 8);
27 C_ASSERT(sizeof(WCHAR) == 2);
28 C_ASSERT(sizeof(wchar_t) == 2);
29 C_ASSERT(sizeof(TAG) == 2);
30 C_ASSERT(sizeof(TAGID) == 4);
31
32
33 extern "C"
34 VOID NTAPI RtlSecondsSince1970ToTime(IN ULONG SecondsSince1970,
35 OUT PLARGE_INTEGER Time);
36
37
38 /***********************************************************************
39 * Helper functions
40 */
41
42
43 // Convert utf8 to utf16:
44 // http://stackoverflow.com/a/7154226/4928207
45
46 bool IsEmptyGuid(const GUID& g)
47 {
48 return !memcmp(&g, &GUID_NULL, sizeof(GUID));
49 }
50
51 void RandomGuid(GUID& g)
52 {
53 BYTE* p = (BYTE*)&g;
54 for (size_t n = 0; n < sizeof(GUID); ++n)
55 p[n] = (BYTE)(rand() % 0xff);
56 }
57
58 // Given a node, return the node value (safe)
59 std::string ToString(XMLHandle node)
60 {
61 XMLText* txtNode = node.FirstChild().ToText();
62 const char* txt = txtNode ? txtNode->Value() : NULL;
63 if (txt)
64 return std::string(txt);
65 return std::string();
66 }
67
68 // Given a node, return the node name (safe)
69 std::string ToNodeName(XMLHandle node)
70 {
71 tinyxml2::XMLNode* raw = node.ToNode();
72 const char* txt = raw ? raw->Value() : NULL;
73 if (txt)
74 return std::string(txt);
75 return std::string();
76 }
77
78 // Read either an attribute, or a child node
79 std::string ReadStringNode(XMLHandle dbNode, const char* nodeName)
80 {
81 tinyxml2::XMLElement* elem = dbNode.ToElement();
82 if (elem)
83 {
84 const char* rawVal = elem->Attribute(nodeName);
85 if (rawVal)
86 return std::string(rawVal);
87 }
88 return ToString(dbNode.FirstChildElement(nodeName));
89 }
90
91 DWORD ReadDWordNode(XMLHandle dbNode, const char* nodeName)
92 {
93 std::string value = ReadStringNode(dbNode, nodeName);
94 int base = 10;
95 if (value.size() > 2 && value[0] == '0' && value[1] == 'x')
96 {
97 base = 16;
98 value = value.substr(2);
99 }
100 return static_cast<DWORD>(strtoul(value.c_str(), NULL, base));
101 }
102
103 unsigned char char2byte(char hexChar, bool* success = NULL)
104 {
105 if (hexChar >= '0' && hexChar <= '9')
106 return hexChar - '0';
107 if (hexChar >= 'A' && hexChar <= 'F')
108 return hexChar - 'A' + 10;
109 if (hexChar >= 'a' && hexChar <= 'f')
110 return hexChar - 'a' + 10;
111
112 if (success)
113 *success = false;
114 return 0;
115 }
116
117 // adapted from wine's ntdll\rtlstr.c rev 1.45
118 static bool StringToGuid(const std::string& str, GUID& guid)
119 {
120 const char *lpszGUID = str.c_str();
121 BYTE* lpOut = (BYTE*)&guid;
122 int i = 0;
123 bool expectBrace = true;
124 while (i <= 37)
125 {
126 switch (i)
127 {
128 case 0:
129 if (*lpszGUID != '{')
130 {
131 i++;
132 expectBrace = false;
133 continue;
134 }
135 break;
136
137 case 9:
138 case 14:
139 case 19:
140 case 24:
141 if (*lpszGUID != '-')
142 return false;
143 break;
144
145 case 37:
146 return expectBrace == (*lpszGUID == '}');
147
148 default:
149 {
150 CHAR ch = *lpszGUID, ch2 = lpszGUID[1];
151 unsigned char byte;
152 bool converted = true;
153
154 byte = char2byte(ch, &converted) << 4 | char2byte(ch2, &converted);
155 if (!converted)
156 return false;
157
158 switch (i)
159 {
160 #ifndef WORDS_BIGENDIAN
161 /* For Big Endian machines, we store the data such that the
162 * dword/word members can be read as DWORDS and WORDS correctly. */
163 /* Dword */
164 case 1:
165 lpOut[3] = byte;
166 break;
167 case 3:
168 lpOut[2] = byte;
169 break;
170 case 5:
171 lpOut[1] = byte;
172 break;
173 case 7:
174 lpOut[0] = byte;
175 lpOut += 4;
176 break;
177 /* Word */
178 case 10:
179 case 15:
180 lpOut[1] = byte;
181 break;
182 case 12:
183 case 17:
184 lpOut[0] = byte;
185 lpOut += 2;
186 break;
187 #endif
188 /* Byte */
189 default:
190 lpOut[0] = byte;
191 lpOut++;
192 break;
193 }
194
195 lpszGUID++; /* Skip 2nd character of byte */
196 i++;
197 }
198 }
199
200 lpszGUID++;
201 i++;
202 }
203 return false;
204 }
205
206 bool ReadGuidNode(XMLHandle dbNode, const char* nodeName, GUID& guid)
207 {
208 std::string value = ReadStringNode(dbNode, nodeName);
209 if (!StringToGuid(value, guid))
210 {
211 memset(&guid, 0, sizeof(GUID));
212 return false;
213 }
214 return true;
215 }
216
217 bool ReadBinaryNode(XMLHandle dbNode, const char* nodeName, std::vector<BYTE>& data)
218 {
219 std::string value = ReadStringNode(dbNode, nodeName);
220 value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());
221
222 size_t length = value.size() / 2;
223 if (length * 2 != value.size())
224 return false;
225
226 data.resize(length);
227 for (size_t n = 0; n < length; ++n)
228 {
229 data[n] = (BYTE)(char2byte(value[n * 2]) << 4 | char2byte(value[(n * 2) + 1]));
230 }
231 return true;
232 }
233
234
235 /***********************************************************************
236 * InExclude
237 */
238
239 bool InExclude::fromXml(XMLHandle dbNode)
240 {
241 Module = ReadStringNode(dbNode, "MODULE");
242 // Special module names: '$' and '*'
243 if (!Module.empty())
244 {
245 Include = ToNodeName(dbNode) == "INCLUDE";
246 return true;
247 }
248 return false;
249 }
250
251 bool InExclude::toSdb(PDB pdb, Database& db)
252 {
253 TAGID tagid = db.BeginWriteListTag(pdb, TAG_INEXCLUD);
254 db.WriteString(pdb, TAG_MODULE, Module, true);
255 if (Include)
256 SdbWriteNULLTag(pdb, TAG_INCLUDE);
257 return !!db.EndWriteListTag(pdb, tagid);
258 }
259
260
261 template<typename T>
262 void ReadGeneric(XMLHandle dbNode, std::list<T>& result, const char* nodeName)
263 {
264 XMLHandle node = dbNode.FirstChildElement(nodeName);
265 while (node.ToNode())
266 {
267 T object;
268 if (object.fromXml(node))
269 result.push_back(object);
270
271 node = node.NextSiblingElement(nodeName);
272 }
273 }
274
275 template<typename T>
276 bool WriteGeneric(PDB pdb, std::list<T>& data, Database& db)
277 {
278 for (typename std::list<T>::iterator it = data.begin(); it != data.end(); ++it)
279 {
280 if (!it->toSdb(pdb, db))
281 return false;
282 }
283 return true;
284 }
285
286
287 /***********************************************************************
288 * ShimRef
289 */
290
291 bool ShimRef::fromXml(XMLHandle dbNode)
292 {
293 Name = ReadStringNode(dbNode, "NAME");
294 CommandLine = ReadStringNode(dbNode, "COMMAND_LINE");
295 ReadGeneric(dbNode, InExcludes, "INCLUDE");
296 ReadGeneric(dbNode, InExcludes, "EXCLUDE");
297 return !Name.empty();
298 }
299
300 bool ShimRef::toSdb(PDB pdb, Database& db)
301 {
302 TAGID tagid = db.BeginWriteListTag(pdb, TAG_SHIM_REF);
303 db.WriteString(pdb, TAG_NAME, Name, true);
304 db.WriteString(pdb, TAG_COMMAND_LINE, CommandLine);
305
306 if (!ShimTagid)
307 ShimTagid = db.FindShimTagid(Name);
308 SdbWriteDWORDTag(pdb, TAG_SHIM_TAGID, ShimTagid);
309 return !!db.EndWriteListTag(pdb, tagid);
310 }
311
312
313 /***********************************************************************
314 * Shim
315 */
316
317 bool Shim::fromXml(XMLHandle dbNode)
318 {
319 Name = ReadStringNode(dbNode, "NAME");
320 DllFile = ReadStringNode(dbNode, "DLLFILE");
321 ReadGuidNode(dbNode, "FIX_ID", FixID);
322 // GENERAL ?
323 // DESCRIPTION_RC_ID
324 ReadGeneric(dbNode, InExcludes, "INCLUDE");
325 ReadGeneric(dbNode, InExcludes, "EXCLUDE");
326 return !Name.empty() && !DllFile.empty();
327 }
328
329 bool Shim::toSdb(PDB pdb, Database& db)
330 {
331 Tagid = db.BeginWriteListTag(pdb, TAG_SHIM);
332 db.InsertShimTagid(Name, Tagid);
333 db.WriteString(pdb, TAG_NAME, Name);
334 db.WriteString(pdb, TAG_DLLFILE, DllFile);
335 if (IsEmptyGuid(FixID))
336 RandomGuid(FixID);
337 db.WriteBinary(pdb, TAG_FIX_ID, FixID);
338 if (!WriteGeneric(pdb, InExcludes, db))
339 return false;
340 return !!db.EndWriteListTag(pdb, Tagid);
341 }
342
343
344 /***********************************************************************
345 * Layer
346 */
347
348 bool Layer::fromXml(XMLHandle dbNode)
349 {
350 Name = ReadStringNode(dbNode, "NAME");
351 ReadGeneric(dbNode, ShimRefs, "SHIM_REF");
352 return true;
353 }
354
355 bool Layer::toSdb(PDB pdb, Database& db)
356 {
357 Tagid = db.BeginWriteListTag(pdb, TAG_LAYER);
358 db.WriteString(pdb, TAG_NAME, Name, true);
359 if (!WriteGeneric(pdb, ShimRefs, db))
360 return false;
361 return !!db.EndWriteListTag(pdb, Tagid);
362 }
363
364
365 /***********************************************************************
366 * MatchingFile
367 */
368
369 bool MatchingFile::fromXml(XMLHandle dbNode)
370 {
371 Name = ReadStringNode(dbNode, "NAME");
372 Size = ReadDWordNode(dbNode, "SIZE");
373 Checksum = ReadDWordNode(dbNode, "CHECKSUM");
374 CompanyName = ReadStringNode(dbNode, "COMPANY_NAME");
375 InternalName = ReadStringNode(dbNode, "INTERNAL_NAME");
376 ProductName = ReadStringNode(dbNode, "PRODUCT_NAME");
377 ProductVersion = ReadStringNode(dbNode, "PRODUCT_VERSION");
378 FileVersion = ReadStringNode(dbNode, "FILE_VERSION");
379 BinFileVersion = ReadStringNode(dbNode, "BIN_FILE_VERSION");
380 LinkDate = ReadStringNode(dbNode, "LINK_DATE");
381 VerLanguage = ReadStringNode(dbNode, "VER_LANGUAGE");
382 FileDescription = ReadStringNode(dbNode, "FILE_DESCRIPTION");
383 OriginalFilename = ReadStringNode(dbNode, "ORIGINAL_FILENAME");
384 UptoBinFileVersion = ReadStringNode(dbNode, "UPTO_BIN_FILE_VERSION");
385 LinkerVersion = ReadStringNode(dbNode, "LINKER_VERSION");
386 return true;
387 }
388
389 bool MatchingFile::toSdb(PDB pdb, Database& db)
390 {
391 TAGID tagid = db.BeginWriteListTag(pdb, TAG_MATCHING_FILE);
392
393 db.WriteString(pdb, TAG_NAME, Name, true);
394 db.WriteDWord(pdb, TAG_SIZE, Size);
395 db.WriteDWord(pdb, TAG_CHECKSUM, Checksum);
396 db.WriteString(pdb, TAG_COMPANY_NAME, CompanyName);
397 db.WriteString(pdb, TAG_INTERNAL_NAME, InternalName);
398 db.WriteString(pdb, TAG_PRODUCT_NAME, ProductName);
399 db.WriteString(pdb, TAG_PRODUCT_VERSION, ProductVersion);
400 db.WriteString(pdb, TAG_FILE_VERSION, FileVersion);
401 if (!BinFileVersion.empty())
402 SHIM_ERR("TAG_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_BIN_FILE_VERSION, BinFileVersion);
403 if (!LinkDate.empty())
404 SHIM_ERR("TAG_LINK_DATE Unimplemented\n"); //db.WriteDWord(pdb, TAG_LINK_DATE, LinkDate);
405 if (!VerLanguage.empty())
406 SHIM_ERR("TAG_VER_LANGUAGE Unimplemented\n"); //db.WriteDWord(pdb, TAG_VER_LANGUAGE, VerLanguage);
407 db.WriteString(pdb, TAG_FILE_DESCRIPTION, FileDescription);
408 db.WriteString(pdb, TAG_ORIGINAL_FILENAME, OriginalFilename);
409 if (!UptoBinFileVersion.empty())
410 SHIM_ERR("TAG_UPTO_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_UPTO_BIN_FILE_VERSION, UptoBinFileVersion);
411 if (!LinkerVersion.empty())
412 SHIM_ERR("TAG_LINKER_VERSION Unimplemented\n"); //db.WriteDWord(pdb, TAG_LINKER_VERSION, LinkerVersion);
413
414
415 return !!db.EndWriteListTag(pdb, tagid);
416 }
417
418
419 /***********************************************************************
420 * Exe
421 */
422
423 bool Exe::fromXml(XMLHandle dbNode)
424 {
425 Name = ReadStringNode(dbNode, "NAME");
426 ReadGuidNode(dbNode, "EXE_ID", ExeID);
427 AppName = ReadStringNode(dbNode, "APP_NAME");
428 Vendor = ReadStringNode(dbNode, "VENDOR");
429
430 ReadGeneric(dbNode, MatchingFiles, "MATCHING_FILE");
431
432 ReadGeneric(dbNode, ShimRefs, "SHIM_REF");
433
434 return !Name.empty();
435 }
436
437 bool Exe::toSdb(PDB pdb, Database& db)
438 {
439 Tagid = db.BeginWriteListTag(pdb, TAG_EXE);
440
441 db.WriteString(pdb, TAG_NAME, Name, true);
442 if (IsEmptyGuid(ExeID))
443 RandomGuid(ExeID);
444 db.WriteBinary(pdb, TAG_EXE_ID, ExeID);
445
446
447 db.WriteString(pdb, TAG_APP_NAME, AppName);
448 db.WriteString(pdb, TAG_VENDOR, Vendor);
449
450 if (!WriteGeneric(pdb, MatchingFiles, db))
451 return false;
452 if (!WriteGeneric(pdb, ShimRefs, db))
453 return false;
454
455 return !!db.EndWriteListTag(pdb, Tagid);
456 }
457
458
459 /***********************************************************************
460 * Database
461 */
462
463 void Database::WriteBinary(PDB pdb, TAG tag, const GUID& guid, bool always)
464 {
465 if (always || !IsEmptyGuid(guid))
466 SdbWriteBinaryTag(pdb, tag, (BYTE*)&guid, sizeof(GUID));
467 }
468
469 void Database::WriteBinary(PDB pdb, TAG tag, const std::vector<BYTE>& data, bool always)
470 {
471 if (always || !data.empty())
472 SdbWriteBinaryTag(pdb, tag, data.data(), data.size());
473 }
474
475 void Database::WriteString(PDB pdb, TAG tag, const sdbstring& str, bool always)
476 {
477 if (always || !str.empty())
478 SdbWriteStringTag(pdb, tag, (LPCWSTR)str.c_str());
479 }
480
481 void Database::WriteString(PDB pdb, TAG tag, const std::string& str, bool always)
482 {
483 WriteString(pdb, tag, sdbstring(str.begin(), str.end()), always);
484 }
485
486 void Database::WriteDWord(PDB pdb, TAG tag, DWORD value, bool always)
487 {
488 if (always || value)
489 SdbWriteDWORDTag(pdb, tag, value);
490 }
491
492 TAGID Database::BeginWriteListTag(PDB pdb, TAG tag)
493 {
494 return SdbBeginWriteListTag(pdb, tag);
495 }
496
497 BOOL Database::EndWriteListTag(PDB pdb, TAGID tagid)
498 {
499 return SdbEndWriteListTag(pdb, tagid);
500 }
501
502 bool Database::fromXml(XMLHandle dbNode)
503 {
504 Name = ReadStringNode(dbNode, "NAME");
505 ReadGuidNode(dbNode, "DATABASE_ID", ID);
506
507 XMLHandle libChild = dbNode.FirstChildElement("LIBRARY").FirstChild();
508 while (libChild.ToNode())
509 {
510 std::string NodeName = ToNodeName(libChild);
511 if (NodeName == "SHIM")
512 {
513 Shim shim;
514 if (shim.fromXml(libChild))
515 Library.Shims.push_back(shim);
516 }
517 else if (NodeName == "FLAG")
518 {
519 SHIM_ERR("Unhanled FLAG type\n");
520 }
521 else if (NodeName == "INCLUDE" || NodeName == "EXCLUDE")
522 {
523 InExclude inex;
524 if (inex.fromXml(libChild))
525 Library.InExcludes.push_back(inex);
526 }
527 libChild = libChild.NextSibling();
528 }
529
530 ReadGeneric(dbNode, Layers, "LAYER");
531 ReadGeneric(dbNode, Exes, "EXE");
532 return true;
533 }
534
535 bool Database::fromXml(const char* fileName)
536 {
537 tinyxml2::XMLDocument doc;
538 tinyxml2::XMLError err = doc.LoadFile(fileName);
539 XMLHandle dbHandle = tinyxml2::XMLHandle(&doc).FirstChildElement("SDB").FirstChildElement("DATABASE");
540 return fromXml(dbHandle);
541 }
542
543 bool Database::toSdb(LPCWSTR path)
544 {
545 PDB pdb = SdbCreateDatabase(path, DOS_PATH);
546 TAGID tidDatabase = BeginWriteListTag(pdb, TAG_DATABASE);
547 LARGE_INTEGER li = { 0 };
548 RtlSecondsSince1970ToTime(time(0), &li);
549 SdbWriteQWORDTag(pdb, TAG_TIME, li.QuadPart);
550 WriteString(pdb, TAG_COMPILER_VERSION, szCompilerVersion);
551 SdbWriteDWORDTag(pdb, TAG_OS_PLATFORM, 1);
552 WriteString(pdb, TAG_NAME, Name, true);
553 if (IsEmptyGuid(ID))
554 {
555 SHIM_WARN("DB has empty ID!\n");
556 RandomGuid(ID);
557 }
558 WriteBinary(pdb, TAG_DATABASE_ID, ID);
559 TAGID tidLibrary = BeginWriteListTag(pdb, TAG_LIBRARY);
560 if (!WriteGeneric(pdb, Library.InExcludes, *this))
561 return false;
562 if (!WriteGeneric(pdb, Library.Shims, *this))
563 return false;
564 EndWriteListTag(pdb, tidLibrary);
565 if (!WriteGeneric(pdb, Layers, *this))
566 return false;
567 if (!WriteGeneric(pdb, Exes, *this))
568 return false;
569 EndWriteListTag(pdb, tidDatabase);
570
571 SdbCloseDatabaseWrite(pdb);
572 return true;
573 }
574
575 static void InsertTagid(const sdbstring& name, TAGID tagid, std::map<sdbstring, TAGID>& lookup, const char* type)
576 {
577 sdbstring nameLower = name;
578 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
579 if (lookup.find(nameLower) != lookup.end())
580 {
581 std::string nameA(name.begin(), name.end());
582 SHIM_WARN("%s '%s' redefined\n", type, nameA.c_str());
583 return;
584 }
585 lookup[nameLower] = tagid;
586 }
587
588 static TAGID FindTagid(const sdbstring& name, const std::map<sdbstring, TAGID>& lookup)
589 {
590 sdbstring nameLower = name;
591 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
592 std::map<sdbstring, TAGID>::const_iterator it = lookup.find(nameLower);
593 if (it == lookup.end())
594 return 0;
595 return it->second;
596 }
597
598 void Database::InsertShimTagid(const sdbstring& name, TAGID tagid)
599 {
600 InsertTagid(name, tagid, KnownShims, "Shim");
601 }
602
603 TAGID Database::FindShimTagid(const sdbstring& name)
604 {
605 return FindTagid(name, KnownShims);
606 }
607
608 void Database::InsertPatchTagid(const sdbstring& name, TAGID tagid)
609 {
610 InsertTagid(name, tagid, KnownPatches, "Patch");
611 }
612
613 TAGID Database::FindPatchTagid(const sdbstring& name)
614 {
615 return FindTagid(name, KnownPatches);
616 }
617
618
619
620 bool xml_2_db(const char* xml, const WCHAR* sdb)
621 {
622 Database db;
623 if (db.fromXml(xml))
624 {
625 return db.toSdb((LPCWSTR)sdb);
626 }
627 return false;
628 }