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