xref: /reactos/sdk/tools/xml2sdb/xml2sdb.cpp (revision c2c66aff)
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 }
629