chore: checkpoint current workspace changes
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <d3dcompiler.h>
|
||||
@@ -37,6 +38,10 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <bcrypt.h>
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
@@ -44,6 +49,158 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
class IncrementalGuidHasher {
|
||||
public:
|
||||
void Append(const void* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hasBytes = true;
|
||||
const auto* bytes = static_cast<const Core::uint8*>(data);
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
m_high ^= static_cast<Core::uint64>(bytes[index]);
|
||||
m_high *= 1099511628211ULL;
|
||||
|
||||
m_low ^= static_cast<Core::uint64>(bytes[index]);
|
||||
m_low *= 1099511628211ULL;
|
||||
}
|
||||
}
|
||||
|
||||
AssetGUID Finish() const {
|
||||
if (!m_hasBytes) {
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
return AssetGUID(m_high, m_low);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_hasBytes = false;
|
||||
Core::uint64 m_high = 14695981039346656037ULL;
|
||||
Core::uint64 m_low = 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool TryComputeFastFileHash(const fs::path& path, Containers::String& outHash) {
|
||||
outHash.Clear();
|
||||
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
std::vector<UCHAR> hashObject;
|
||||
std::vector<UCHAR> digest;
|
||||
|
||||
auto cleanup = [&]() {
|
||||
if (hash != nullptr) {
|
||||
BCryptDestroyHash(hash);
|
||||
hash = nullptr;
|
||||
}
|
||||
if (algorithm != nullptr) {
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
algorithm = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
NTSTATUS status = BCryptOpenAlgorithmProvider(
|
||||
&algorithm,
|
||||
BCRYPT_SHA256_ALGORITHM,
|
||||
nullptr,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD hashObjectSize = 0;
|
||||
DWORD bytesCopied = 0;
|
||||
status = BCryptGetProperty(
|
||||
algorithm,
|
||||
BCRYPT_OBJECT_LENGTH,
|
||||
reinterpret_cast<PUCHAR>(&hashObjectSize),
|
||||
sizeof(hashObjectSize),
|
||||
&bytesCopied,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status) || hashObjectSize == 0) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD digestSize = 0;
|
||||
status = BCryptGetProperty(
|
||||
algorithm,
|
||||
BCRYPT_HASH_LENGTH,
|
||||
reinterpret_cast<PUCHAR>(&digestSize),
|
||||
sizeof(digestSize),
|
||||
&bytesCopied,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status) || digestSize == 0) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
hashObject.resize(hashObjectSize);
|
||||
digest.resize(digestSize);
|
||||
status = BCryptCreateHash(
|
||||
algorithm,
|
||||
&hash,
|
||||
hashObject.data(),
|
||||
static_cast<ULONG>(hashObject.size()),
|
||||
nullptr,
|
||||
0,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<char, 256 * 1024> buffer = {};
|
||||
bool hasBytes = false;
|
||||
while (input) {
|
||||
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
const std::streamsize bytesRead = input.gcount();
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasBytes = true;
|
||||
status = BCryptHashData(
|
||||
hash,
|
||||
reinterpret_cast<PUCHAR>(buffer.data()),
|
||||
static_cast<ULONG>(bytesRead),
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBytes) {
|
||||
cleanup();
|
||||
outHash = HashBytesToAssetGUID(nullptr, 0).ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
status = BCryptFinishHash(
|
||||
hash,
|
||||
digest.data(),
|
||||
static_cast<ULONG>(digest.size()),
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
outHash = HashBytesToAssetGUID(digest.data(), digest.size()).ToString();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
@@ -86,6 +243,34 @@ constexpr const char* kSourceAssetDbFileName = "assets.db";
|
||||
constexpr const char* kArtifactDbFileName = "artifacts.db";
|
||||
constexpr const char* kLegacySourceAssetDbDirectory = "SourceAssetDB";
|
||||
constexpr const char* kLegacyArtifactDbDirectory = "ArtifactDB";
|
||||
constexpr Core::uint32 kArtifactDbSchemaVersion = 2;
|
||||
|
||||
ArtifactStorageKind InferArtifactStorageKind(const Containers::String& projectRoot,
|
||||
const AssetDatabase::ArtifactRecord& record) {
|
||||
if (!record.mainArtifactPath.Empty()) {
|
||||
const Containers::String absoluteMainArtifactPath =
|
||||
NormalizeArtifactPathString(fs::path(projectRoot.CStr()) / record.mainArtifactPath.CStr());
|
||||
if (IsArtifactContainerFile(absoluteMainArtifactPath)) {
|
||||
return ArtifactStorageKind::SingleFileContainer;
|
||||
}
|
||||
}
|
||||
|
||||
return record.artifactDirectory.Empty()
|
||||
? ArtifactStorageKind::Unknown
|
||||
: ArtifactStorageKind::LegacyDirectory;
|
||||
}
|
||||
|
||||
Containers::String BuildArtifactMainEntryLoadPath(const Containers::String& artifactMainPath,
|
||||
ArtifactStorageKind storageKind,
|
||||
const Containers::String& mainEntryName) {
|
||||
if (storageKind == ArtifactStorageKind::SingleFileContainer &&
|
||||
!artifactMainPath.Empty() &&
|
||||
!mainEntryName.Empty()) {
|
||||
return BuildArtifactContainerEntryPath(artifactMainPath, mainEntryName);
|
||||
}
|
||||
|
||||
return artifactMainPath;
|
||||
}
|
||||
|
||||
struct ModelSubAssetManifestEntry {
|
||||
LocalID localID = kInvalidLocalID;
|
||||
@@ -107,10 +292,18 @@ void PopulateResolvedAssetResult(const Containers::String& projectRoot,
|
||||
outAsset.relativePath = sourceRecord.relativePath;
|
||||
outAsset.assetGuid = sourceRecord.guid;
|
||||
outAsset.resourceType = artifactRecord.resourceType;
|
||||
outAsset.artifactDirectory =
|
||||
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactMainPath =
|
||||
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactStorageKind = artifactRecord.storageKind;
|
||||
outAsset.mainEntryName = artifactRecord.mainEntryName;
|
||||
if (!artifactRecord.artifactDirectory.Empty()) {
|
||||
outAsset.artifactDirectory = ToContainersString(
|
||||
(fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
|
||||
}
|
||||
outAsset.artifactMainPath = ToContainersString(
|
||||
(fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactMainEntryPath = BuildArtifactMainEntryLoadPath(
|
||||
outAsset.artifactMainPath,
|
||||
artifactRecord.storageKind,
|
||||
artifactRecord.mainEntryName);
|
||||
outAsset.mainLocalID = artifactRecord.mainLocalID;
|
||||
}
|
||||
|
||||
@@ -1753,8 +1946,25 @@ void AssetDatabase::LoadArtifactDB() {
|
||||
}
|
||||
|
||||
std::string line;
|
||||
Core::uint32 schemaVersion = 1;
|
||||
while (std::getline(input, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '#') {
|
||||
const std::string schemaToken = "schema=";
|
||||
const size_t schemaTokenPosition = line.find(schemaToken);
|
||||
if (schemaTokenPosition != std::string::npos) {
|
||||
const size_t valueStart = schemaTokenPosition + schemaToken.length();
|
||||
const std::string valueText = line.substr(valueStart);
|
||||
if (!valueText.empty()) {
|
||||
schemaVersion = static_cast<Core::uint32>(std::stoul(valueText));
|
||||
}
|
||||
} else if (line.find("storageKind") != std::string::npos &&
|
||||
line.find("mainEntryName") != std::string::npos) {
|
||||
schemaVersion = std::max(schemaVersion, kArtifactDbSchemaVersion);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1776,7 +1986,26 @@ void AssetDatabase::LoadArtifactDB() {
|
||||
record.sourceFileSize = static_cast<Core::uint64>(std::stoull(fields[9]));
|
||||
record.sourceWriteTime = fields.size() > 10 ? static_cast<Core::uint64>(std::stoull(fields[10])) : 0;
|
||||
record.mainLocalID = fields.size() > 11 ? static_cast<LocalID>(std::stoull(fields[11])) : kMainAssetLocalID;
|
||||
for (size_t index = 12; index + 3 < fields.size(); index += 4) {
|
||||
size_t dependencyFieldStart = 12;
|
||||
if (schemaVersion >= kArtifactDbSchemaVersion && fields.size() > 13) {
|
||||
record.storageKind = static_cast<ArtifactStorageKind>(std::stoul(fields[12]));
|
||||
record.mainEntryName = ToContainersString(fields[13]);
|
||||
dependencyFieldStart = 14;
|
||||
}
|
||||
|
||||
if (record.storageKind == ArtifactStorageKind::Unknown) {
|
||||
record.storageKind = InferArtifactStorageKind(m_projectRoot, record);
|
||||
}
|
||||
if (record.mainEntryName.Empty() &&
|
||||
record.storageKind == ArtifactStorageKind::SingleFileContainer) {
|
||||
record.mainEntryName = "main";
|
||||
}
|
||||
if (record.artifactDirectory.Empty() && !record.mainArtifactPath.Empty()) {
|
||||
record.artifactDirectory =
|
||||
NormalizePathString(fs::path(record.mainArtifactPath.CStr()).parent_path());
|
||||
}
|
||||
|
||||
for (size_t index = dependencyFieldStart; index + 3 < fields.size(); index += 4) {
|
||||
ArtifactDependencyRecord dependency;
|
||||
dependency.path = ToContainersString(fields[index + 0]);
|
||||
dependency.hash = ToContainersString(fields[index + 1]);
|
||||
@@ -1801,7 +2030,8 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
return;
|
||||
}
|
||||
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||
output << "# schema=" << kArtifactDbSchemaVersion << "\n";
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\tstorageKind\tmainEntryName\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||
for (const auto& [guid, record] : m_artifactsByGuid) {
|
||||
output << EscapeField(ToStdString(record.artifactKey)) << '\t'
|
||||
<< EscapeField(ToStdString(record.assetGuid.ToString())) << '\t'
|
||||
@@ -1814,7 +2044,9 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
<< EscapeField(ToStdString(record.metaHash)) << '\t'
|
||||
<< record.sourceFileSize << '\t'
|
||||
<< record.sourceWriteTime << '\t'
|
||||
<< record.mainLocalID;
|
||||
<< record.mainLocalID << '\t'
|
||||
<< static_cast<Core::uint32>(record.storageKind) << '\t'
|
||||
<< EscapeField(ToStdString(record.mainEntryName));
|
||||
for (const ArtifactDependencyRecord& dependency : record.dependencies) {
|
||||
output << '\t' << EscapeField(ToStdString(dependency.path))
|
||||
<< '\t' << EscapeField(ToStdString(dependency.hash))
|
||||
@@ -2494,8 +2726,10 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Texture;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2553,8 +2787,10 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Material;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2743,8 +2979,10 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Model;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2809,8 +3047,10 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Shader;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2874,8 +3114,10 @@ bool AssetDatabase::ImportGaussianSplatAsset(const SourceAssetRecord& sourceReco
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::GaussianSplat;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2953,8 +3195,10 @@ bool AssetDatabase::ImportVolumeFieldAsset(const SourceAssetRecord& sourceRecord
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::VolumeField;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -3040,8 +3284,10 @@ bool AssetDatabase::ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = resourceType;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -3315,19 +3561,31 @@ Containers::String AssetDatabase::ReadWholeFileText(const fs::path& path) {
|
||||
}
|
||||
|
||||
Containers::String AssetDatabase::ComputeFileHash(const fs::path& path) {
|
||||
#if defined(_WIN32)
|
||||
Containers::String fastHash;
|
||||
if (TryComputeFastFileHash(path, fastHash)) {
|
||||
return fastHash;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
std::vector<Core::uint8> bytes(
|
||||
(std::istreambuf_iterator<char>(input)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (bytes.empty()) {
|
||||
return HashBytesToAssetGUID(nullptr, 0).ToString();
|
||||
IncrementalGuidHasher hasher;
|
||||
std::array<char, 64 * 1024> buffer = {};
|
||||
while (input) {
|
||||
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
const std::streamsize bytesRead = input.gcount();
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasher.Append(buffer.data(), static_cast<size_t>(bytesRead));
|
||||
}
|
||||
|
||||
return HashBytesToAssetGUID(bytes.data(), bytes.size()).ToString();
|
||||
return hasher.Finish().ToString();
|
||||
}
|
||||
|
||||
Core::uint64 AssetDatabase::GetFileSizeValue(const fs::path& path) {
|
||||
|
||||
@@ -341,9 +341,17 @@ AssetImportService::ImportedAsset AssetImportService::ConvertResolvedAsset(
|
||||
importedAsset.relativePath = resolvedAsset.relativePath;
|
||||
importedAsset.assetGuid = resolvedAsset.assetGuid;
|
||||
importedAsset.resourceType = resolvedAsset.resourceType;
|
||||
importedAsset.artifactMainPath = resolvedAsset.artifactMainPath;
|
||||
importedAsset.artifactMainEntryPath = resolvedAsset.artifactMainEntryPath;
|
||||
importedAsset.runtimeLoadPath =
|
||||
resolvedAsset.artifactReady ? resolvedAsset.artifactMainPath : resolvedAsset.absolutePath;
|
||||
resolvedAsset.artifactReady
|
||||
? (!resolvedAsset.artifactMainEntryPath.Empty()
|
||||
? resolvedAsset.artifactMainEntryPath
|
||||
: resolvedAsset.artifactMainPath)
|
||||
: resolvedAsset.absolutePath;
|
||||
importedAsset.artifactDirectory = resolvedAsset.artifactDirectory;
|
||||
importedAsset.artifactStorageKind = resolvedAsset.artifactStorageKind;
|
||||
importedAsset.mainEntryName = resolvedAsset.mainEntryName;
|
||||
importedAsset.mainLocalID = resolvedAsset.mainLocalID;
|
||||
return importedAsset;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -53,16 +54,23 @@ Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::S
|
||||
}
|
||||
|
||||
Containers::String IResourceLoader::GetExtension(const Containers::String& path) {
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
Containers::String ext;
|
||||
size_t dotPos = Containers::String::npos;
|
||||
for (size_t i = path.Length(); i > 0; --i) {
|
||||
if (path[i - 1] == '.') {
|
||||
for (size_t i = normalizedPath.Length(); i > 0; --i) {
|
||||
if (normalizedPath[i - 1] == '.') {
|
||||
dotPos = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dotPos != Containers::String::npos) {
|
||||
ext = path.Substring(dotPos + 1);
|
||||
ext = normalizedPath.Substring(dotPos + 1);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user