[SHIMENG] Partial rewrite to make the code easier to maintain.
[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
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.5.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 if (!Module.empty())
245 {
246 Include = dbNode.FirstChildElement("INCLUDE").ToNode() != NULL;
247 if (!Include)
248 {
249 tinyxml2::XMLElement* elem = dbNode.ToElement();
250 if (elem)
251 {
252 Include |= (elem->Attribute("INCLUDE") != NULL);
253 }
254 }
255 // $ = ??
256 // *
257 return true;
258 }
259 return false;
260 }
261
262 bool InExclude::toSdb(PDB pdb, Database& db)
263 {
264 TAGID tagid = db.BeginWriteListTag(pdb, TAG_INEXCLUD);
265 db.WriteString(pdb, TAG_MODULE, Module, true);
266 if (Include)
267 SdbWriteNULLTag(pdb, TAG_INCLUDE);
268 return !!db.EndWriteListTag(pdb, tagid);
269 }
270
271
272 template<typename T>
273 void ReadGeneric(XMLHandle dbNode, std::list<T>& result, const char* nodeName)
274 {
275 XMLHandle node = dbNode.FirstChildElement(nodeName);
276 while (node.ToNode())
277 {
278 T object;
279 if (object.fromXml(node))
280 result.push_back(object);
281
282 node = node.NextSiblingElement(nodeName);
283 }
284 }
285
286 template<typename T>
287 bool WriteGeneric(PDB pdb, std::list<T>& data, Database& db)
288 {
289 for (typename std::list<T>::iterator it = data.begin(); it != data.end(); ++it)
290 {
291 if (!it->toSdb(pdb, db))
292 return false;
293 }
294 return true;
295 }
296
297
298 /***********************************************************************
299 * ShimRef
300 */
301
302 bool ShimRef::fromXml(XMLHandle dbNode)
303 {
304 Name = ReadStringNode(dbNode, "NAME");
305 CommandLine = ReadStringNode(dbNode, "COMMAND_LINE");
306 ReadGeneric(dbNode, InExcludes, "INEXCLUDE");
307 return !Name.empty();
308 }
309
310 bool ShimRef::toSdb(PDB pdb, Database& db)
311 {
312 TAGID tagid = db.BeginWriteListTag(pdb, TAG_SHIM_REF);
313 db.WriteString(pdb, TAG_NAME, Name, true);
314 db.WriteString(pdb, TAG_COMMAND_LINE, CommandLine);
315
316 if (!ShimTagid)
317 ShimTagid = db.FindShimTagid(Name);
318 SdbWriteDWORDTag(pdb, TAG_SHIM_TAGID, ShimTagid);
319 return !!db.EndWriteListTag(pdb, tagid);
320 }
321
322
323 /***********************************************************************
324 * Shim
325 */
326
327 bool Shim::fromXml(XMLHandle dbNode)
328 {
329 Name = ReadStringNode(dbNode, "NAME");
330 DllFile = ReadStringNode(dbNode, "DLLFILE");
331 ReadGuidNode(dbNode, "FIX_ID", FixID);
332 // GENERAL ?
333 // DESCRIPTION_RC_ID
334 ReadGeneric(dbNode, InExcludes, "INEXCLUDE");
335 return !Name.empty() && !DllFile.empty();
336 }
337
338 bool Shim::toSdb(PDB pdb, Database& db)
339 {
340 Tagid = db.BeginWriteListTag(pdb, TAG_SHIM);
341 db.InsertShimTagid(Name, Tagid);
342 db.WriteString(pdb, TAG_NAME, Name);
343 db.WriteString(pdb, TAG_DLLFILE, DllFile);
344 if (IsEmptyGuid(FixID))
345 RandomGuid(FixID);
346 db.WriteBinary(pdb, TAG_FIX_ID, FixID);
347 if (!WriteGeneric(pdb, InExcludes, db))
348 return false;
349 return !!db.EndWriteListTag(pdb, Tagid);
350 }
351
352
353 /***********************************************************************
354 * Layer
355 */
356
357 bool Layer::fromXml(XMLHandle dbNode)
358 {
359 Name = ReadStringNode(dbNode, "NAME");
360 ReadGeneric(dbNode, ShimRefs, "SHIM_REF");
361 return true;
362 }
363
364 bool Layer::toSdb(PDB pdb, Database& db)
365 {
366 Tagid = db.BeginWriteListTag(pdb, TAG_LAYER);
367 db.WriteString(pdb, TAG_NAME, Name, true);
368 if (!WriteGeneric(pdb, ShimRefs, db))
369 return false;
370 return !!db.EndWriteListTag(pdb, Tagid);
371 }
372
373
374 /***********************************************************************
375 * MatchingFile
376 */
377
378 bool MatchingFile::fromXml(XMLHandle dbNode)
379 {
380 Name = ReadStringNode(dbNode, "NAME");
381 Size = ReadDWordNode(dbNode, "SIZE");
382 Checksum = ReadDWordNode(dbNode, "CHECKSUM");
383 CompanyName = ReadStringNode(dbNode, "COMPANY_NAME");
384 InternalName = ReadStringNode(dbNode, "INTERNAL_NAME");
385 ProductName = ReadStringNode(dbNode, "PRODUCT_NAME");
386 ProductVersion = ReadStringNode(dbNode, "PRODUCT_VERSION");
387 FileVersion = ReadStringNode(dbNode, "FILE_VERSION");
388 BinFileVersion = ReadStringNode(dbNode, "BIN_FILE_VERSION");
389 LinkDate = ReadStringNode(dbNode, "LINK_DATE");
390 VerLanguage = ReadStringNode(dbNode, "VER_LANGUAGE");
391 FileDescription = ReadStringNode(dbNode, "FILE_DESCRIPTION");
392 OriginalFilename = ReadStringNode(dbNode, "ORIGINAL_FILENAME");
393 UptoBinFileVersion = ReadStringNode(dbNode, "UPTO_BIN_FILE_VERSION");
394 LinkerVersion = ReadStringNode(dbNode, "LINKER_VERSION");
395 return true;
396 }
397
398 bool MatchingFile::toSdb(PDB pdb, Database& db)
399 {
400 TAGID tagid = db.BeginWriteListTag(pdb, TAG_MATCHING_FILE);
401
402 db.WriteString(pdb, TAG_NAME, Name, true);
403 db.WriteDWord(pdb, TAG_SIZE, Size);
404 db.WriteDWord(pdb, TAG_CHECKSUM, Checksum);
405 db.WriteString(pdb, TAG_COMPANY_NAME, CompanyName);
406 db.WriteString(pdb, TAG_INTERNAL_NAME, InternalName);
407 db.WriteString(pdb, TAG_PRODUCT_NAME, ProductName);
408 db.WriteString(pdb, TAG_PRODUCT_VERSION, ProductVersion);
409 db.WriteString(pdb, TAG_FILE_VERSION, FileVersion);
410 if (!BinFileVersion.empty())
411 SHIM_ERR("TAG_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_BIN_FILE_VERSION, BinFileVersion);
412 if (!LinkDate.empty())
413 SHIM_ERR("TAG_LINK_DATE Unimplemented\n"); //db.WriteDWord(pdb, TAG_LINK_DATE, LinkDate);
414 if (!VerLanguage.empty())
415 SHIM_ERR("TAG_VER_LANGUAGE Unimplemented\n"); //db.WriteDWord(pdb, TAG_VER_LANGUAGE, VerLanguage);
416 db.WriteString(pdb, TAG_FILE_DESCRIPTION, FileDescription);
417 db.WriteString(pdb, TAG_ORIGINAL_FILENAME, OriginalFilename);
418 if (!UptoBinFileVersion.empty())
419 SHIM_ERR("TAG_UPTO_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_UPTO_BIN_FILE_VERSION, UptoBinFileVersion);
420 if (!LinkerVersion.empty())
421 SHIM_ERR("TAG_LINKER_VERSION Unimplemented\n"); //db.WriteDWord(pdb, TAG_LINKER_VERSION, LinkerVersion);
422
423
424 return !!db.EndWriteListTag(pdb, tagid);
425 }
426
427
428 /***********************************************************************
429 * Exe
430 */
431
432 bool Exe::fromXml(XMLHandle dbNode)
433 {
434 Name = ReadStringNode(dbNode, "NAME");
435 ReadGuidNode(dbNode, "EXE_ID", ExeID);
436 AppName = ReadStringNode(dbNode, "APP_NAME");
437 Vendor = ReadStringNode(dbNode, "VENDOR");
438
439 ReadGeneric(dbNode, MatchingFiles, "MATCHING_FILE");
440
441 ReadGeneric(dbNode, ShimRefs, "SHIM_REF");
442
443 return !Name.empty();
444 }
445
446 bool Exe::toSdb(PDB pdb, Database& db)
447 {
448 Tagid = db.BeginWriteListTag(pdb, TAG_EXE);
449
450 db.WriteString(pdb, TAG_NAME, Name, true);
451 if (IsEmptyGuid(ExeID))
452 RandomGuid(ExeID);
453 db.WriteBinary(pdb, TAG_EXE_ID, ExeID);
454
455
456 db.WriteString(pdb, TAG_APP_NAME, AppName);
457 db.WriteString(pdb, TAG_VENDOR, Vendor);
458
459 if (!WriteGeneric(pdb, MatchingFiles, db))
460 return false;
461 if (!WriteGeneric(pdb, ShimRefs, db))
462 return false;
463
464 return !!db.EndWriteListTag(pdb, Tagid);
465 }
466
467
468 /***********************************************************************
469 * Database
470 */
471
472 void Database::WriteBinary(PDB pdb, TAG tag, const GUID& guid, bool always)
473 {
474 if (always || !IsEmptyGuid(guid))
475 SdbWriteBinaryTag(pdb, tag, (BYTE*)&guid, sizeof(GUID));
476 }
477
478 void Database::WriteBinary(PDB pdb, TAG tag, const std::vector<BYTE>& data, bool always)
479 {
480 if (always || !data.empty())
481 SdbWriteBinaryTag(pdb, tag, data.data(), data.size());
482 }
483
484 void Database::WriteString(PDB pdb, TAG tag, const sdbstring& str, bool always)
485 {
486 if (always || !str.empty())
487 SdbWriteStringTag(pdb, tag, (LPCWSTR)str.c_str());
488 }
489
490 void Database::WriteString(PDB pdb, TAG tag, const std::string& str, bool always)
491 {
492 WriteString(pdb, tag, sdbstring(str.begin(), str.end()), always);
493 }
494
495 void Database::WriteDWord(PDB pdb, TAG tag, DWORD value, bool always)
496 {
497 if (always || value)
498 SdbWriteDWORDTag(pdb, tag, value);
499 }
500
501 TAGID Database::BeginWriteListTag(PDB db, TAG tag)
502 {
503 return SdbBeginWriteListTag(db, tag);
504 }
505
506 BOOL Database::EndWriteListTag(PDB db, TAGID tagid)
507 {
508 return SdbEndWriteListTag(db, tagid);
509 }
510
511 bool Database::fromXml(XMLHandle dbNode)
512 {
513 Name = ReadStringNode(dbNode, "NAME");
514 ReadGuidNode(dbNode, "DATABASE_ID", ID);
515
516 XMLHandle libChild = dbNode.FirstChildElement("LIBRARY").FirstChild();
517 while (libChild.ToNode())
518 {
519 std::string NodeName = ToNodeName(libChild);
520 if (NodeName == "SHIM")
521 {
522 Shim shim;
523 if (shim.fromXml(libChild))
524 Library.Shims.push_back(shim);
525 }
526 else if (NodeName == "FLAG")
527 {
528 SHIM_ERR("Unhanled FLAG type\n");
529 }
530 libChild = libChild.NextSibling();
531 }
532
533 ReadGeneric(dbNode, Layers, "LAYER");
534 ReadGeneric(dbNode, Exes, "EXE");
535 return true;
536 }
537
538 bool Database::fromXml(const char* fileName)
539 {
540 tinyxml2::XMLDocument doc;
541 tinyxml2::XMLError err = doc.LoadFile(fileName);
542 XMLHandle dbHandle = tinyxml2::XMLHandle(&doc).FirstChildElement("SDB").FirstChildElement("DATABASE");
543 return fromXml(dbHandle);
544 }
545
546 bool Database::toSdb(LPCWSTR path)
547 {
548 PDB pdb = SdbCreateDatabase(path, DOS_PATH);
549 TAGID tidDatabase = BeginWriteListTag(pdb, TAG_DATABASE);
550 LARGE_INTEGER li = { 0 };
551 RtlSecondsSince1970ToTime(time(0), &li);
552 SdbWriteQWORDTag(pdb, TAG_TIME, li.QuadPart);
553 WriteString(pdb, TAG_COMPILER_VERSION, szCompilerVersion);
554 SdbWriteDWORDTag(pdb, TAG_OS_PLATFORM, 1);
555 WriteString(pdb, TAG_NAME, Name, true);
556 if (IsEmptyGuid(ID))
557 {
558 SHIM_WARN("DB has empty ID!\n");
559 RandomGuid(ID);
560 }
561 WriteBinary(pdb, TAG_DATABASE_ID, ID);
562 TAGID tidLibrary = BeginWriteListTag(pdb, TAG_LIBRARY);
563 if (!WriteGeneric(pdb, Library.Shims, *this))
564 return false;
565 EndWriteListTag(pdb, tidLibrary);
566 if (!WriteGeneric(pdb, Layers, *this))
567 return false;
568 if (!WriteGeneric(pdb, Exes, *this))
569 return false;
570 EndWriteListTag(pdb, tidDatabase);
571
572 SdbCloseDatabaseWrite(pdb);
573 return true;
574 }
575
576 static void InsertTagid(const sdbstring& name, TAGID tagid, std::map<sdbstring, TAGID>& lookup, const char* type)
577 {
578 sdbstring nameLower = name;
579 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
580 if (lookup.find(nameLower) != lookup.end())
581 {
582 std::string nameA(name.begin(), name.end());
583 SHIM_WARN("%s '%s' redefined\n", type, nameA.c_str());
584 return;
585 }
586 lookup[nameLower] = tagid;
587 }
588
589 static TAGID FindTagid(const sdbstring& name, const std::map<sdbstring, TAGID>& lookup)
590 {
591 sdbstring nameLower = name;
592 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
593 std::map<sdbstring, TAGID>::const_iterator it = lookup.find(nameLower);
594 if (it == lookup.end())
595 return 0;
596 return it->second;
597 }
598
599 void Database::InsertShimTagid(const sdbstring& name, TAGID tagid)
600 {
601 InsertTagid(name, tagid, KnownShims, "Shim");
602 }
603
604 TAGID Database::FindShimTagid(const sdbstring& name)
605 {
606 return FindTagid(name, KnownShims);
607 }
608
609 void Database::InsertPatchTagid(const sdbstring& name, TAGID tagid)
610 {
611 InsertTagid(name, tagid, KnownPatches, "Patch");
612 }
613
614 TAGID Database::FindPatchTagid(const sdbstring& name)
615 {
616 return FindTagid(name, KnownPatches);
617 }
618
619
620
621 bool xml_2_db(const char* xml, const WCHAR* sdb)
622 {
623 Database db;
624 if (db.fromXml(xml))
625 {
626 return db.toSdb((LPCWSTR)sdb);
627 }
628 return false;
629 }