/* * PROJECT: xml2sdb * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: Conversion functions from xml -> db * COPYRIGHT: Copyright 2016-2018 Mark Jansen (mark.jansen@reactos.org) */ #include "xml2sdb.h" #include "sdbpapi.h" #include "tinyxml2.h" #include #include using tinyxml2::XMLText; static const GUID GUID_NULL = { 0 }; static const char szCompilerVersion[] = "1.7.0.1"; #if !defined(C_ASSERT) #define C_ASSERT(expr) extern char (*c_assert(void)) [(expr) ? 1 : -1] #endif C_ASSERT(sizeof(GUID) == 16); C_ASSERT(sizeof(ULONG) == 4); C_ASSERT(sizeof(LARGE_INTEGER) == 8); C_ASSERT(sizeof(WCHAR) == 2); C_ASSERT(sizeof(wchar_t) == 2); C_ASSERT(sizeof(TAG) == 2); C_ASSERT(sizeof(TAGID) == 4); extern "C" VOID NTAPI RtlSecondsSince1970ToTime(IN ULONG SecondsSince1970, OUT PLARGE_INTEGER Time); /*********************************************************************** * Helper functions */ // Convert utf8 to utf16: // http://stackoverflow.com/a/7154226/4928207 bool IsEmptyGuid(const GUID& g) { return !memcmp(&g, &GUID_NULL, sizeof(GUID)); } void RandomGuid(GUID& g) { BYTE* p = (BYTE*)&g; for (size_t n = 0; n < sizeof(GUID); ++n) p[n] = (BYTE)(rand() % 0xff); } // Given a node, return the node value (safe) std::string ToString(XMLHandle node) { XMLText* txtNode = node.FirstChild().ToText(); const char* txt = txtNode ? txtNode->Value() : NULL; if (txt) return std::string(txt); return std::string(); } // Given a node, return the node name (safe) std::string ToNodeName(XMLHandle node) { tinyxml2::XMLNode* raw = node.ToNode(); const char* txt = raw ? raw->Value() : NULL; if (txt) return std::string(txt); return std::string(); } // Read either an attribute, or a child node std::string ReadStringNode(XMLHandle dbNode, const char* nodeName) { tinyxml2::XMLElement* elem = dbNode.ToElement(); if (elem) { const char* rawVal = elem->Attribute(nodeName); if (rawVal) return std::string(rawVal); } return ToString(dbNode.FirstChildElement(nodeName)); } DWORD ReadQWordNode(XMLHandle dbNode, const char* nodeName) { std::string value = ReadStringNode(dbNode, nodeName); int base = 10; if (value.size() > 2 && value[0] == '0' && value[1] == 'x') { base = 16; value = value.substr(2); } return static_cast(strtoul(value.c_str(), NULL, base)); } DWORD ReadDWordNode(XMLHandle dbNode, const char* nodeName) { return static_cast(ReadQWordNode(dbNode, nodeName)); } unsigned char char2byte(char hexChar, bool* success = NULL) { if (hexChar >= '0' && hexChar <= '9') return hexChar - '0'; if (hexChar >= 'A' && hexChar <= 'F') return hexChar - 'A' + 10; if (hexChar >= 'a' && hexChar <= 'f') return hexChar - 'a' + 10; if (success) *success = false; return 0; } // adapted from wine's ntdll\rtlstr.c rev 1.45 static bool StringToGuid(const std::string& str, GUID& guid) { const char *lpszGUID = str.c_str(); BYTE* lpOut = (BYTE*)&guid; int i = 0; bool expectBrace = true; while (i <= 37) { switch (i) { case 0: if (*lpszGUID != '{') { i++; expectBrace = false; continue; } break; case 9: case 14: case 19: case 24: if (*lpszGUID != '-') return false; break; case 37: return expectBrace == (*lpszGUID == '}'); default: { CHAR ch = *lpszGUID, ch2 = lpszGUID[1]; unsigned char byte; bool converted = true; byte = char2byte(ch, &converted) << 4 | char2byte(ch2, &converted); if (!converted) return false; switch (i) { #ifndef WORDS_BIGENDIAN /* For Big Endian machines, we store the data such that the * dword/word members can be read as DWORDS and WORDS correctly. */ /* Dword */ case 1: lpOut[3] = byte; break; case 3: lpOut[2] = byte; break; case 5: lpOut[1] = byte; break; case 7: lpOut[0] = byte; lpOut += 4; break; /* Word */ case 10: case 15: lpOut[1] = byte; break; case 12: case 17: lpOut[0] = byte; lpOut += 2; break; #endif /* Byte */ default: lpOut[0] = byte; lpOut++; break; } lpszGUID++; /* Skip 2nd character of byte */ i++; } } lpszGUID++; i++; } return false; } bool ReadGuidNode(XMLHandle dbNode, const char* nodeName, GUID& guid) { std::string value = ReadStringNode(dbNode, nodeName); if (!StringToGuid(value, guid)) { memset(&guid, 0, sizeof(GUID)); return false; } return true; } bool ReadBinaryNode(XMLHandle dbNode, const char* nodeName, std::vector& data) { std::string value = ReadStringNode(dbNode, nodeName); value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); size_t length = value.size() / 2; if (length * 2 != value.size()) return false; data.resize(length); for (size_t n = 0; n < length; ++n) { data[n] = (BYTE)(char2byte(value[n * 2]) << 4 | char2byte(value[(n * 2) + 1])); } return true; } /*********************************************************************** * InExclude */ bool InExclude::fromXml(XMLHandle dbNode) { Module = ReadStringNode(dbNode, "MODULE"); // Special module names: '$' and '*' if (!Module.empty()) { Include = ToNodeName(dbNode) == "INCLUDE"; return true; } return false; } bool InExclude::toSdb(PDB pdb, Database& db) { TAGID tagid = db.BeginWriteListTag(pdb, TAG_INEXCLUD); db.WriteString(pdb, TAG_MODULE, Module, true); if (Include) SdbWriteNULLTag(pdb, TAG_INCLUDE); return !!db.EndWriteListTag(pdb, tagid); } template void ReadGeneric(XMLHandle dbNode, std::list& result, const char* nodeName) { XMLHandle node = dbNode.FirstChildElement(nodeName); while (node.ToNode()) { T object; if (object.fromXml(node)) result.push_back(object); node = node.NextSiblingElement(nodeName); } } template bool WriteGeneric(PDB pdb, std::list& data, Database& db) { for (typename std::list::iterator it = data.begin(); it != data.end(); ++it) { if (!it->toSdb(pdb, db)) return false; } return true; } /*********************************************************************** * ShimRef */ bool ShimRef::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); CommandLine = ReadStringNode(dbNode, "COMMAND_LINE"); ReadGeneric(dbNode, InExcludes, "INCLUDE"); ReadGeneric(dbNode, InExcludes, "EXCLUDE"); return !Name.empty(); } bool ShimRef::toSdb(PDB pdb, Database& db) { TAGID tagid = db.BeginWriteListTag(pdb, TAG_SHIM_REF); db.WriteString(pdb, TAG_NAME, Name, true); db.WriteString(pdb, TAG_COMMAND_LINE, CommandLine); if (!ShimTagid) ShimTagid = db.FindShimTagid(Name); SdbWriteDWORDTag(pdb, TAG_SHIM_TAGID, ShimTagid); return !!db.EndWriteListTag(pdb, tagid); } /*********************************************************************** * FlagRef */ bool FlagRef::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); return !Name.empty(); } bool FlagRef::toSdb(PDB pdb, Database& db) { TAGID tagid = db.BeginWriteListTag(pdb, TAG_FLAG_REF); db.WriteString(pdb, TAG_NAME, Name, true); if (!FlagTagid) FlagTagid = db.FindFlagTagid(Name); SdbWriteDWORDTag(pdb, TAG_FLAG_TAGID, FlagTagid); return !!db.EndWriteListTag(pdb, tagid); } /*********************************************************************** * Shim */ bool Shim::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); DllFile = ReadStringNode(dbNode, "DLLFILE"); ReadGuidNode(dbNode, "FIX_ID", FixID); // GENERAL ? // DESCRIPTION_RC_ID ReadGeneric(dbNode, InExcludes, "INCLUDE"); ReadGeneric(dbNode, InExcludes, "EXCLUDE"); return !Name.empty() && !DllFile.empty(); } bool Shim::toSdb(PDB pdb, Database& db) { Tagid = db.BeginWriteListTag(pdb, TAG_SHIM); db.InsertShimTagid(Name, Tagid); db.WriteString(pdb, TAG_NAME, Name); db.WriteString(pdb, TAG_DLLFILE, DllFile); if (IsEmptyGuid(FixID)) RandomGuid(FixID); db.WriteBinary(pdb, TAG_FIX_ID, FixID); if (!WriteGeneric(pdb, InExcludes, db)) return false; return !!db.EndWriteListTag(pdb, Tagid); } /*********************************************************************** * Flag */ bool Flag::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); KernelFlags = ReadQWordNode(dbNode, "FLAG_MASK_KERNEL"); UserFlags = ReadQWordNode(dbNode, "FLAG_MASK_USER"); ProcessParamFlags = ReadQWordNode(dbNode, "FLAG_PROCESSPARAM"); return !Name.empty(); } bool Flag::toSdb(PDB pdb, Database& db) { Tagid = db.BeginWriteListTag(pdb, TAG_FLAG); db.InsertFlagTagid(Name, Tagid); db.WriteString(pdb, TAG_NAME, Name, true); db.WriteQWord(pdb, TAG_FLAG_MASK_KERNEL, KernelFlags); db.WriteQWord(pdb, TAG_FLAG_MASK_USER, UserFlags); db.WriteQWord(pdb, TAG_FLAG_PROCESSPARAM, ProcessParamFlags); return !!db.EndWriteListTag(pdb, Tagid); } /*********************************************************************** * Data */ #ifndef REG_SZ #define REG_SZ 1 //#define REG_BINARY 3 #define REG_DWORD 4 #define REG_QWORD 11 #endif bool Data::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); StringData = ReadStringNode(dbNode, "DATA_STRING"); if (!StringData.empty()) { DataType = REG_SZ; return !Name.empty(); } DWordData = ReadDWordNode(dbNode, "DATA_DWORD"); if (DWordData) { DataType = REG_DWORD; return !Name.empty(); } QWordData = ReadQWordNode(dbNode, "DATA_QWORD"); if (QWordData) { DataType = REG_QWORD; return !Name.empty(); } SHIM_ERR("Data node (%s) without value!\n", Name.c_str()); return false; } bool Data::toSdb(PDB pdb, Database& db) { Tagid = db.BeginWriteListTag(pdb, TAG_DATA); db.WriteString(pdb, TAG_NAME, Name, true); db.WriteDWord(pdb, TAG_DATA_VALUETYPE, DataType, true); switch (DataType) { case REG_SZ: db.WriteString(pdb, TAG_DATA_STRING, StringData); break; case REG_DWORD: db.WriteDWord(pdb, TAG_DATA_DWORD, DWordData); break; case REG_QWORD: db.WriteQWord(pdb, TAG_DATA_QWORD, QWordData); break; default: SHIM_ERR("Data node (%s) with unknown type (0x%x)\n", Name.c_str(), DataType); return false; } return !!db.EndWriteListTag(pdb, Tagid); } /*********************************************************************** * Layer */ bool Layer::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); ReadGeneric(dbNode, ShimRefs, "SHIM_REF"); ReadGeneric(dbNode, FlagRefs, "FLAG_REF"); ReadGeneric(dbNode, Datas, "DATA"); return true; } bool Layer::toSdb(PDB pdb, Database& db) { Tagid = db.BeginWriteListTag(pdb, TAG_LAYER); db.WriteString(pdb, TAG_NAME, Name, true); if (!WriteGeneric(pdb, ShimRefs, db)) return false; if (!WriteGeneric(pdb, FlagRefs, db)) return false; if (!WriteGeneric(pdb, Datas, db)) return false; return !!db.EndWriteListTag(pdb, Tagid); } /*********************************************************************** * MatchingFile */ bool MatchingFile::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); Size = ReadDWordNode(dbNode, "SIZE"); Checksum = ReadDWordNode(dbNode, "CHECKSUM"); CompanyName = ReadStringNode(dbNode, "COMPANY_NAME"); InternalName = ReadStringNode(dbNode, "INTERNAL_NAME"); ProductName = ReadStringNode(dbNode, "PRODUCT_NAME"); ProductVersion = ReadStringNode(dbNode, "PRODUCT_VERSION"); FileVersion = ReadStringNode(dbNode, "FILE_VERSION"); BinFileVersion = ReadStringNode(dbNode, "BIN_FILE_VERSION"); LinkDate = ReadDWordNode(dbNode, "LINK_DATE"); VerLanguage = ReadStringNode(dbNode, "VER_LANGUAGE"); FileDescription = ReadStringNode(dbNode, "FILE_DESCRIPTION"); OriginalFilename = ReadStringNode(dbNode, "ORIGINAL_FILENAME"); UptoBinFileVersion = ReadStringNode(dbNode, "UPTO_BIN_FILE_VERSION"); LinkerVersion = ReadDWordNode(dbNode, "LINKER_VERSION"); return true; } bool MatchingFile::toSdb(PDB pdb, Database& db) { TAGID tagid = db.BeginWriteListTag(pdb, TAG_MATCHING_FILE); db.WriteString(pdb, TAG_NAME, Name, true); db.WriteDWord(pdb, TAG_SIZE, Size); db.WriteDWord(pdb, TAG_CHECKSUM, Checksum); db.WriteString(pdb, TAG_COMPANY_NAME, CompanyName); db.WriteString(pdb, TAG_INTERNAL_NAME, InternalName); db.WriteString(pdb, TAG_PRODUCT_NAME, ProductName); db.WriteString(pdb, TAG_PRODUCT_VERSION, ProductVersion); db.WriteString(pdb, TAG_FILE_VERSION, FileVersion); if (!BinFileVersion.empty()) SHIM_ERR("TAG_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_BIN_FILE_VERSION, BinFileVersion); db.WriteDWord(pdb, TAG_LINK_DATE, LinkDate); if (!VerLanguage.empty()) SHIM_ERR("TAG_VER_LANGUAGE Unimplemented\n"); //db.WriteDWord(pdb, TAG_VER_LANGUAGE, VerLanguage); db.WriteString(pdb, TAG_FILE_DESCRIPTION, FileDescription); db.WriteString(pdb, TAG_ORIGINAL_FILENAME, OriginalFilename); if (!UptoBinFileVersion.empty()) SHIM_ERR("TAG_UPTO_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(pdb, TAG_UPTO_BIN_FILE_VERSION, UptoBinFileVersion); db.WriteDWord(pdb, TAG_LINKER_VERSION, LinkerVersion); return !!db.EndWriteListTag(pdb, tagid); } /*********************************************************************** * Exe */ bool Exe::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); ReadGuidNode(dbNode, "EXE_ID", ExeID); AppName = ReadStringNode(dbNode, "APP_NAME"); Vendor = ReadStringNode(dbNode, "VENDOR"); ReadGeneric(dbNode, MatchingFiles, "MATCHING_FILE"); ReadGeneric(dbNode, ShimRefs, "SHIM_REF"); ReadGeneric(dbNode, FlagRefs, "FLAG_REF"); return !Name.empty(); } bool Exe::toSdb(PDB pdb, Database& db) { Tagid = db.BeginWriteListTag(pdb, TAG_EXE); db.WriteString(pdb, TAG_NAME, Name, true); if (IsEmptyGuid(ExeID)) RandomGuid(ExeID); db.WriteBinary(pdb, TAG_EXE_ID, ExeID); db.WriteString(pdb, TAG_APP_NAME, AppName); db.WriteString(pdb, TAG_VENDOR, Vendor); if (!WriteGeneric(pdb, MatchingFiles, db)) return false; if (!WriteGeneric(pdb, ShimRefs, db)) return false; if (!WriteGeneric(pdb, FlagRefs, db)) return false; return !!db.EndWriteListTag(pdb, Tagid); } /*********************************************************************** * Database */ void Database::WriteBinary(PDB pdb, TAG tag, const GUID& guid, bool always) { if (always || !IsEmptyGuid(guid)) SdbWriteBinaryTag(pdb, tag, (BYTE*)&guid, sizeof(GUID)); } void Database::WriteBinary(PDB pdb, TAG tag, const std::vector& data, bool always) { if (always || !data.empty()) SdbWriteBinaryTag(pdb, tag, data.data(), data.size()); } void Database::WriteString(PDB pdb, TAG tag, const sdbstring& str, bool always) { if (always || !str.empty()) SdbWriteStringTag(pdb, tag, (LPCWSTR)str.c_str()); } void Database::WriteString(PDB pdb, TAG tag, const std::string& str, bool always) { WriteString(pdb, tag, sdbstring(str.begin(), str.end()), always); } void Database::WriteDWord(PDB pdb, TAG tag, DWORD value, bool always) { if (always || value) SdbWriteDWORDTag(pdb, tag, value); } void Database::WriteQWord(PDB pdb, TAG tag, QWORD value, bool always) { if (always || value) SdbWriteQWORDTag(pdb, tag, value); } TAGID Database::BeginWriteListTag(PDB pdb, TAG tag) { return SdbBeginWriteListTag(pdb, tag); } BOOL Database::EndWriteListTag(PDB pdb, TAGID tagid) { return SdbEndWriteListTag(pdb, tagid); } bool Database::fromXml(XMLHandle dbNode) { Name = ReadStringNode(dbNode, "NAME"); ReadGuidNode(dbNode, "DATABASE_ID", ID); XMLHandle libChild = dbNode.FirstChildElement("LIBRARY").FirstChild(); while (libChild.ToNode()) { std::string NodeName = ToNodeName(libChild); if (NodeName == "SHIM") { Shim shim; if (shim.fromXml(libChild)) Library.Shims.push_back(shim); } else if (NodeName == "FLAG") { Flag flag; if (flag.fromXml(libChild)) Library.Flags.push_back(flag); } else if (NodeName == "INCLUDE" || NodeName == "EXCLUDE") { InExclude inex; if (inex.fromXml(libChild)) Library.InExcludes.push_back(inex); } libChild = libChild.NextSibling(); } ReadGeneric(dbNode, Layers, "LAYER"); ReadGeneric(dbNode, Exes, "EXE"); return true; } bool Database::fromXml(const char* fileName) { tinyxml2::XMLDocument doc; tinyxml2::XMLError err = doc.LoadFile(fileName); XMLHandle dbHandle = tinyxml2::XMLHandle(&doc).FirstChildElement("SDB").FirstChildElement("DATABASE"); return fromXml(dbHandle); } bool Database::toSdb(LPCWSTR path) { PDB pdb = SdbCreateDatabase(path, DOS_PATH); TAGID tidDatabase = BeginWriteListTag(pdb, TAG_DATABASE); LARGE_INTEGER li = { 0 }; RtlSecondsSince1970ToTime(time(0), &li); SdbWriteQWORDTag(pdb, TAG_TIME, li.QuadPart); WriteString(pdb, TAG_COMPILER_VERSION, szCompilerVersion); SdbWriteDWORDTag(pdb, TAG_OS_PLATFORM, 1); WriteString(pdb, TAG_NAME, Name, true); if (IsEmptyGuid(ID)) { SHIM_WARN("DB has empty ID!\n"); RandomGuid(ID); } WriteBinary(pdb, TAG_DATABASE_ID, ID); TAGID tidLibrary = BeginWriteListTag(pdb, TAG_LIBRARY); if (!WriteGeneric(pdb, Library.InExcludes, *this)) return false; if (!WriteGeneric(pdb, Library.Shims, *this)) return false; if (!WriteGeneric(pdb, Library.Flags, *this)) return false; EndWriteListTag(pdb, tidLibrary); if (!WriteGeneric(pdb, Layers, *this)) return false; if (!WriteGeneric(pdb, Exes, *this)) return false; EndWriteListTag(pdb, tidDatabase); SdbCloseDatabaseWrite(pdb); return true; } static void InsertTagid(const sdbstring& name, TAGID tagid, std::map& lookup, const char* type) { sdbstring nameLower = name; std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); if (lookup.find(nameLower) != lookup.end()) { std::string nameA(name.begin(), name.end()); SHIM_WARN("%s '%s' redefined\n", type, nameA.c_str()); return; } lookup[nameLower] = tagid; } static TAGID FindTagid(const sdbstring& name, const std::map& lookup) { sdbstring nameLower = name; std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); std::map::const_iterator it = lookup.find(nameLower); if (it == lookup.end()) return 0; return it->second; } void Database::InsertShimTagid(const sdbstring& name, TAGID tagid) { InsertTagid(name, tagid, KnownShims, "Shim"); } TAGID Database::FindShimTagid(const sdbstring& name) { return FindTagid(name, KnownShims); } void Database::InsertPatchTagid(const sdbstring& name, TAGID tagid) { InsertTagid(name, tagid, KnownPatches, "Patch"); } TAGID Database::FindPatchTagid(const sdbstring& name) { return FindTagid(name, KnownPatches); } void Database::InsertFlagTagid(const sdbstring& name, TAGID tagid) { InsertTagid(name, tagid, KnownFlags, "Flag"); } TAGID Database::FindFlagTagid(const sdbstring& name) { return FindTagid(name, KnownFlags); } bool xml_2_db(const char* xml, const WCHAR* sdb) { Database db; if (db.fromXml(xml)) { return db.toSdb((LPCWSTR)sdb); } return false; }