reactos/sdk/tools/xml2sdb/xml2sdb.cpp

774 lines
20 KiB
C++

/*
* 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 <time.h>
#include <algorithm>
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<QWORD>(strtoul(value.c_str(), NULL, base));
}
DWORD ReadDWordNode(XMLHandle dbNode, const char* nodeName)
{
return static_cast<DWORD>(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<BYTE>& 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<typename T>
void ReadGeneric(XMLHandle dbNode, std::list<T>& 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<typename T>
bool WriteGeneric(PDB pdb, std::list<T>& data, Database& db)
{
for (typename std::list<T>::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<BYTE>& 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<sdbstring, TAGID>& 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<sdbstring, TAGID>& lookup)
{
sdbstring nameLower = name;
std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
std::map<sdbstring, TAGID>::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;
}