Hooks
static bool BeforeCvDllDatabaseUtility_ValidateGameDatabase(CvDllDatabaseUtility* self, bool& result);static void AfterCvDllDatabaseUtility_ValidateGameDatabase(CvDllDatabaseUtility* self, bool& result);static bool ReplaceCvDllDatabaseUtility_ValidateGameDatabase(bool (*original)(CvDllDatabaseUtility*), CvDllDatabaseUtility* self);// CivHooks.h gives this hook point no types, so nothing checks these hooks' parameters.host->RegisterHook(host->pluginIndex, CivHooks::CvDllDatabaseUtility_ValidateGameDatabase::ID, CIV_HOOK_BEFORE, (void*)&BeforeCvDllDatabaseUtility_ValidateGameDatabase);host->RegisterHook(host->pluginIndex, CivHooks::CvDllDatabaseUtility_ValidateGameDatabase::ID, CIV_HOOK_AFTER, (void*)&AfterCvDllDatabaseUtility_ValidateGameDatabase);host->RegisterHook(host->pluginIndex, CivHooks::CvDllDatabaseUtility_ValidateGameDatabase::ID, CIV_HOOK_REPLACE, (void*)&ReplaceCvDllDatabaseUtility_ValidateGameDatabase);
Code
450bool CvDllDatabaseUtility::ValidateGameDatabase()451{452 //This function contains a suite of useful game database unit tests that will453 //ensure the database conforms to certain unique rules of Civ5.454 if(!gDLL->ShouldValidateGameDatabase())455 {456 LogMsg("*** SKIPPING Game Database Validation. ****");457 LogMsg("You can enable it by setting 'ValidateGameDatabase = 1' in config.ini");458 return true;459 }460461 cvStopWatch kPerfTest("Validating Game Database", "xml-perf.log");462 bool bError = false;463464 LogMsg("**** Validating Game Database *****");465466 //Test that all Tables w/ 'ID' column start at 0 and not 1.467 {468 cvStopWatch watch("Ensure All Tables with 'ID' column start at 0");469 Database::Results kTables("name");470 if(DB.SelectAt(kTables, "sqlite_master", "type", "table"))471 {472 while(kTables.Step())473 {474 const char* szTableName = kTables.GetText(0);475 if(DB.Count(szTableName) > 0)476 {477 //Test if table has 'ID' column478 bool bHasIDColumn = false;479 {480 //Execute "select ID from <table_name> limit 1;481 //If there's a SQL error, it's most likely due to a lack of an 'id' column.482 char szSQL[512];483 sprintf_s(szSQL, "pragma table_info(%s)", szTableName);484 Database::Results kResults;485 DB.Execute(kResults, szSQL);486 while(kResults.Step())487 {488 const char* szName = kResults.GetText("name");489 if(strcmp(szTableName, szName) == 0)490 {491 bHasIDColumn = true;492 break;493 }494495 }496 }497498 if(bHasIDColumn)499 {500 Database::SingleResult kTest;501 if(!DB.SelectAt(kTest, szTableName, "ID", 0))502 {503 //Table has 'ID' column and contains data but does not use ID 0.504 char szError[512];505 sprintf_s(szError, "Table '%s' contains 'ID' column that starts at 1 instead of 0.", szTableName);506 LogMsg(szError);507 bError = true;508 }509 }510511 }512 }513 }514 }515516 //Validate FK constraints517 {518 cvStopWatch watch("Validate FK Constraints");519 DB.ValidateFKConstraints();520 }521522 LogMsg("Performing Localization Checks");523 {524 cvStopWatch watch("Localization Checks");525 LogMsg("Checking Tag Format...");526 LogMsg("Note: Tags must only use [A-Z_] characters, start with 'TXT_KEY_', and be under 128 characters long.");527528 Database::Results kLanguages;529 if(DB.SelectAll(kLanguages, "Languages"))530 {531 while(kLanguages.Step())532 {533 const char* szTagColumnName = kLanguages.GetText("TagColumnName");534 const char* szTableName = kLanguages.GetText("TableName");535536 Database::Results kResults;537 char szSQL[512];538 sprintf_s(szSQL, "SELECT DISTINCT %s from %s", szTagColumnName, szTableName);539 if(DB.Execute(kResults, szSQL))540 {541 bool bFirst = true;542 while(kResults.Step())543 {544 const char* szTag = kResults.GetText(0);545 const size_t len = strlen(szTag);546 if(len > 128)547 {548 if(bFirst)549 {550 LogMsg("In table %s...", szTableName);551 bFirst = false;552 }553554 bError = true;555 LogMsg("Tag (%s) has size greater than 127 characters.", szTag);556 }557 else558 {559 if(strncmp("TXT_KEY_", szTag, 8) != 0)560 {561 if(bFirst)562 {563 LogMsg("In table %s...", szTableName);564 bFirst = false;565 }566567 bError = true;568 LogMsg("Tag (%s) does not start with 'TXT_KEY_'", szTag);569 }570571 for(size_t i = 0; i < len; ++i)572 {573 char ch = szTag[i];574 if(ch != '_' && !((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')))575 {576 if(bFirst)577 {578 LogMsg("In table %s...", szTableName);579 bFirst = false;580 }581582 bError = true;583 LogMsg("Tag (%s) contains invalid character (%.1s)", szTag, &ch);584 }585 }586 }587 }588 }589 }590 }591 }592593 LogMsg("Validating UnitGameplay2DScripts");594 {595 cvStopWatch watch("Validating UnitGameplay2DScripts");596 Database::Results kResults;597 if(DB.Execute(kResults, "Select Type from Units where not exists (select 1 from UnitGameplay2DScripts where UnitType = Units.Type limit 1)"))598 {599 while(kResults.Step())600 {601 const char* szUnitType = kResults.GetText(0);602 LogMsg("Missing Entry for %s", szUnitType);603 bError = true;604 }605 }606 }607608 if(bError)609 {610 LogMsg("**** VALIDATION FAILED *****");611 }612 else613 {614 LogMsg("**** Validation Success *****");615 }616617 kPerfTest.EndPerfTest();618 LogMsg("Validation Took %f seconds", kPerfTest.GetDeltaInSeconds());619620 return !bError;621}