Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826/* * PROJECT: xml2sdb * LICENSE: MIT (https://spdx.org/licenses/MIT) * PURPOSE: Conversion functions from xml -> db * COPYRIGHT: Copyright 2016-2025 Mark Jansen <mark.jansen@reactos.org> */
#include "xml2sdb.h"#include "sdbpapi.h"#include "tinyxml2.h"#include <time.h>#include <algorithm>#include <sstream>
using tinyxml2::XMLText;
static const GUID GUID_NULL = { 0 };static const char szCompilerVersion[] = "1.8.0.0";
#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 */str_to_flag platform_to_flag[] = { {"X86", PLATFORM_X86}, {"I386", PLATFORM_X86}, {"AMD64", PLATFORM_AMD64}, {"ANY", PLATFORM_ANY}, {nullptr, 0},};
DWORDstr_to_enum(const std::string& str, const str_to_flag* table){ DWORD value = 0; std::istringstream iss(str); std::string item; while (std::getline(iss, item, ',')) { std::string trimmedItem = item; trimmedItem.erase(remove_if(trimmedItem.begin(), trimmedItem.end(), isspace), trimmedItem.end()); std::transform(trimmedItem.begin(), trimmedItem.end(), trimmedItem.begin(), ::toupper); for (const str_to_flag* p = table; p->name; ++p) { if (trimmedItem == p->name) { value |= p->flag; break; } } } return value;}
// 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 nodestd::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));}
QWORD 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>(strtoull(value.c_str(), NULL, base));}
DWORD ReadDWordNode(XMLHandle dbNode, const char* nodeName){ return static_cast<DWORD>(ReadQWordNode(dbNode, nodeName));}
PlatformType ReadPlatformNode(XMLHandle dbNode, const char *nodeName){ std::string value = ReadStringNode(dbNode, nodeName); if (value.empty()) return PLATFORM_ANY; DWORD platform = str_to_enum(value, platform_to_flag); return static_cast<PlatformType>(platform);}
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.45static 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(Database& db){ TAGID tagid = db.BeginWriteListTag(TAG_INEXCLUD); db.WriteString(TAG_MODULE, Module, true); if (Include) db.WriteNull(TAG_INCLUDE); return !!db.EndWriteListTag(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>void ReadGeneric(XMLHandle dbNode, std::list<T>& result, const char* nodeName, PlatformType platform){ XMLHandle node = dbNode.FirstChildElement(nodeName); while (node.ToNode()) { T object; if (object.fromXml(node) && ((object.Platform & platform) != PLATFORM_NONE)) result.push_back(object);
node = node.NextSiblingElement(nodeName); }}
template<typename T>bool WriteGeneric(std::list<T>& data, Database& db){ for (typename std::list<T>::iterator it = data.begin(); it != data.end(); ++it) { if (!it->toSdb(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(Database& db){ TAGID tagid = db.BeginWriteListTag(TAG_SHIM_REF); db.WriteString(TAG_NAME, Name, true); db.WriteString(TAG_COMMAND_LINE, CommandLine);
if (!ShimTagid) ShimTagid = db.FindShimTagid(Name); db.WriteDWord(TAG_SHIM_TAGID, ShimTagid); return !!db.EndWriteListTag(tagid);}
/*********************************************************************** * FlagRef */
bool FlagRef::fromXml(XMLHandle dbNode){ Name = ReadStringNode(dbNode, "NAME"); return !Name.empty();}
bool FlagRef::toSdb(Database& db){ TAGID tagid = db.BeginWriteListTag(TAG_FLAG_REF); db.WriteString(TAG_NAME, Name, true);
if (!FlagTagid) FlagTagid = db.FindFlagTagid(Name); db.WriteDWord(TAG_FLAG_TAGID, FlagTagid); return !!db.EndWriteListTag(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(Database& db){ Tagid = db.BeginWriteListTag(TAG_SHIM); db.InsertShimTagid(Name, Tagid); db.WriteString(TAG_NAME, Name); db.WriteString(TAG_DLLFILE, DllFile); if (IsEmptyGuid(FixID)) RandomGuid(FixID); db.WriteBinary(TAG_FIX_ID, FixID); if (!WriteGeneric(InExcludes, db)) return false; return !!db.EndWriteListTag(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(Database& db){ Tagid = db.BeginWriteListTag(TAG_FLAG); db.InsertFlagTagid(Name, Tagid); db.WriteString(TAG_NAME, Name, true);
db.WriteQWord(TAG_FLAG_MASK_KERNEL, KernelFlags); db.WriteQWord(TAG_FLAG_MASK_USER, UserFlags); db.WriteQWord(TAG_FLAG_PROCESSPARAM, ProcessParamFlags);
return !!db.EndWriteListTag(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(Database& db){ Tagid = db.BeginWriteListTag(TAG_DATA); db.WriteString(TAG_NAME, Name, true); db.WriteDWord(TAG_DATA_VALUETYPE, DataType, true); switch (DataType) { case REG_SZ: db.WriteString(TAG_DATA_STRING, StringData); break; case REG_DWORD: db.WriteDWord(TAG_DATA_DWORD, DWordData); break; case REG_QWORD: db.WriteQWord(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(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(Database& db){ Tagid = db.BeginWriteListTag(TAG_LAYER); db.WriteString(TAG_NAME, Name, true); if (!WriteGeneric(ShimRefs, db)) return false; if (!WriteGeneric(FlagRefs, db)) return false; if (!WriteGeneric(Datas, db)) return false; return !!db.EndWriteListTag(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(Database& db){ TAGID tagid = db.BeginWriteListTag(TAG_MATCHING_FILE);
db.WriteString(TAG_NAME, Name, true); db.WriteDWord(TAG_SIZE, Size); db.WriteDWord(TAG_CHECKSUM, Checksum); db.WriteString(TAG_COMPANY_NAME, CompanyName); db.WriteString(TAG_INTERNAL_NAME, InternalName); db.WriteString(TAG_PRODUCT_NAME, ProductName); db.WriteString(TAG_PRODUCT_VERSION, ProductVersion); db.WriteString(TAG_FILE_VERSION, FileVersion); if (!BinFileVersion.empty()) SHIM_ERR("TAG_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(TAG_BIN_FILE_VERSION, BinFileVersion); db.WriteDWord(TAG_LINK_DATE, LinkDate); if (!VerLanguage.empty()) SHIM_ERR("TAG_VER_LANGUAGE Unimplemented\n"); //db.WriteDWord(TAG_VER_LANGUAGE, VerLanguage); db.WriteString(TAG_FILE_DESCRIPTION, FileDescription); db.WriteString(TAG_ORIGINAL_FILENAME, OriginalFilename); if (!UptoBinFileVersion.empty()) SHIM_ERR("TAG_UPTO_BIN_FILE_VERSION Unimplemented\n"); //db.WriteQWord(TAG_UPTO_BIN_FILE_VERSION, UptoBinFileVersion); db.WriteDWord(TAG_LINKER_VERSION, LinkerVersion);
return !!db.EndWriteListTag(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");
Platform = ReadPlatformNode(dbNode, "RUNTIME_PLATFORM");
return !Name.empty();}
bool Exe::toSdb(Database& db){ Tagid = db.BeginWriteListTag(TAG_EXE);
db.WriteString(TAG_NAME, Name, true); if (IsEmptyGuid(ExeID)) RandomGuid(ExeID); db.WriteBinary(TAG_EXE_ID, ExeID);
db.WriteString(TAG_APP_NAME, AppName); db.WriteString(TAG_VENDOR, Vendor);
if (!WriteGeneric(MatchingFiles, db)) return false; if (!WriteGeneric(ShimRefs, db)) return false; if (!WriteGeneric(FlagRefs, db)) return false;
return !!db.EndWriteListTag(Tagid);}
/*********************************************************************** * Database */
void Database::WriteBinary(TAG tag, const GUID& guid, bool always){ if (always || !IsEmptyGuid(guid)) SdbWriteBinaryTag(pdb, tag, (BYTE*)&guid, sizeof(GUID));}
void Database::WriteBinary(TAG tag, const std::vector<BYTE>& data, bool always){ if (always || !data.empty()) SdbWriteBinaryTag(pdb, tag, data.data(), data.size());}
void Database::WriteString(TAG tag, const sdbstring& str, bool always){ if (always || !str.empty()) SdbWriteStringTag(pdb, tag, (LPCWSTR)str.c_str());}
void Database::WriteString(TAG tag, const std::string& str, bool always){ WriteString(tag, sdbstring(str.begin(), str.end()), always);}
void Database::WriteDWord(TAG tag, DWORD value, bool always){ if (always || value) SdbWriteDWORDTag(pdb, tag, value);}
void Database::WriteQWord(TAG tag, QWORD value, bool always){ if (always || value) SdbWriteQWORDTag(pdb, tag, value);}
void Database::WriteNull(TAG tag){ SdbWriteNULLTag(pdb, tag);}
TAGID Database::BeginWriteListTag(TAG tag){ return SdbBeginWriteListTag(pdb, tag);}
BOOL Database::EndWriteListTag(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) && ((shim.Platform & platform) != PLATFORM_NONE)) 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", platform); ReadGeneric(dbNode, Exes, "EXE", platform); return true;}
bool Database::fromXml(const char* fileName, PlatformType platform_){ tinyxml2::XMLDocument doc; tinyxml2::XMLError err = doc.LoadFile(fileName); XMLHandle dbHandle = tinyxml2::XMLHandle(&doc).FirstChildElement("SDB").FirstChildElement("DATABASE"); platform = platform_; return fromXml(dbHandle);}
bool Database::toSdb(LPCWSTR path){ pdb = SdbCreateDatabase(path, DOS_PATH); TAGID tidDatabase = BeginWriteListTag(TAG_DATABASE); LARGE_INTEGER li = { 0 }; RtlSecondsSince1970ToTime(time(0), &li); WriteQWord(TAG_TIME, li.QuadPart); WriteString(TAG_COMPILER_VERSION, szCompilerVersion); WriteDWord(TAG_OS_PLATFORM, platform); WriteString(TAG_NAME, Name, true); if (IsEmptyGuid(ID)) { SHIM_WARN("DB has empty ID!\n"); RandomGuid(ID); } WriteBinary(TAG_DATABASE_ID, ID); TAGID tidLibrary = BeginWriteListTag(TAG_LIBRARY); if (!WriteGeneric(Library.InExcludes, *this)) return false; if (!WriteGeneric(Library.Shims, *this)) return false; if (!WriteGeneric(Library.Flags, *this)) return false; EndWriteListTag(tidLibrary); if (!WriteGeneric(Layers, *this)) return false; if (!WriteGeneric(Exes, *this)) return false; EndWriteListTag(tidDatabase);
SdbCloseDatabaseWrite(pdb); pdb = nullptr; 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);}