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