#include "NCoreTests.h" #include "IUIRelay.h" #include "Core/GameProject/CGameProject.h" #include "Core/GameProject/CResourceEntry.h" #include "Core/GameProject/CResourceIterator.h" #include "Core/Resource/Cooker/CResourceCooker.h" namespace NCoreTests { /** Checks for a parameter in the commandline stream */ const char* ParseParameter(const char* pkParmName, int argc, char* argv[]) { const uint kParmLen = strlen(pkParmName); for (int i=0; i= kParmLen+2 && argv[i][kParmLen] == '=') { return &argv[i][kParmLen+1]; } } } // Couldn't find the parameter. return nullptr; } /** Checks for the existence of a token in the commandline stream */ bool ParseToken(const char* pkToken, int argc, char* argv[]) { for (int i=0; i::ConvertStringToValue(pkType); bool AllowDump = ParseToken("-allowdump", argc, argv); if( Type == EResourceType::Invalid ) { gpUIRelay->ShowMessageBox("ValidateCooker", "Usage: ValidateCooker -type= [-allowdump] [-project=]"); } else if( gpUIRelay->OpenProject(ParseParameter("-project", argc, argv)) ) { ValidateCooker(Type, AllowDump); } return true; } // No test being run. return false; } /** Validate all cooker output for the given resource type matches the original asset data */ bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents) { debugf( "Validating output of %s cooker...", TEnumReflection::ConvertValueToString(ResourceType) ); // There must be a project loaded CResourceStore* pStore = gpResourceStore; CGameProject* pProject = (pStore ? pStore->Project() : nullptr); if (!pProject) { errorf("Cooker unit test failed; no project loaded"); return false; } TString ResourcesDir = pProject->ResourcesDir(false); uint NumValid = 0, NumInvalid = 0; // Iterate through all resources for (CResourceIterator It(pStore); It; ++It) { if (It->ResourceType() != ResourceType || !It->HasCookedVersion()) continue; // Get original cooked data TString CookedPath = It->CookedAssetPath(true); CFileInStream FileStream(ResourcesDir / CookedPath, EEndian::BigEndian); if (!FileStream.IsValid()) continue; std::vector OriginalData( FileStream.Size() ); FileStream.ReadBytes(OriginalData.data(), OriginalData.size()); FileStream.Close(); // Generate new cooked data std::vector NewData; CVectorOutStream MemoryStream(&NewData, EEndian::BigEndian); CResourceCooker::CookResource(*It, MemoryStream); // Start our comparison by making sure the sizes match up const uint kAlignment = (It->Game() >= EGame::Corruption ? 64 : 32); const uint kAlignedOriginalSize = ALIGN( (uint) OriginalData.size(), kAlignment ); const uint kAlignedNewSize = ALIGN( (uint) NewData.size(), kAlignment ); const char* pkInvalidReason = ""; bool IsValid = false; if( kAlignedOriginalSize == kAlignedNewSize && OriginalData.size() >= NewData.size() ) { // Compare actual data. Note that the original asset can have alignment padding // at the end, which is applied by the pak but usually preserved in extracted // files. We do not include this in the comparison as missing padding does not // indicate malformed data. uint DataSize = Math::Min(OriginalData.size(), NewData.size()); if( memcmp(OriginalData.data(), NewData.data(), DataSize) == 0 ) { // Verify any missing data at the end is padding. bool MissingData = false; if( OriginalData.size() > NewData.size() ) { for( uint i=DataSize; i= 100 ) { debugf( "Test aborted; at least 100 invalid resources. Checked %d resources, %d passed, %d failed", NumValid + NumInvalid, NumValid, NumInvalid ); return false; } } // Test complete bool TestSuccess = (NumInvalid == 0); debugf( "Test %s; checked %d resources, %d passed, %d failed", TestSuccess ? "SUCCEEDED" : "FAILED", NumValid + NumInvalid, NumValid, NumInvalid ); return TestSuccess; } } // end namespace NCoreTests