295 lines
9.8 KiB
C++
295 lines
9.8 KiB
C++
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
|
|
|
|
|
#include "VrmJson.h"
|
|
#include "VrmConvert.h"
|
|
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
|
|
#include "rapidjson/schema.h"
|
|
#include "rapidjson/stringbuffer.h"
|
|
#include "rapidjson/error/en.h"
|
|
|
|
bool VRMIsValid(const uint8_t* pData, size_t size) {
|
|
VRMConverter j;
|
|
if (j.Init(pData, size, nullptr)) {
|
|
return j.ValidateSchema();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool VRMIsVRM10(RAPIDJSON_NAMESPACE::Document &doc) {
|
|
if (doc.HasMember("extensions")) {
|
|
if (doc["extensions"].HasMember("VRMC_vrm")) {
|
|
return true;
|
|
}
|
|
if (doc["extensions"].HasMember("VRMC_vrm_animation")) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VRMIsVRM10(const uint8_t* pData, size_t size) {
|
|
VRMConverter j;
|
|
if (j.Init(pData, size, nullptr) == false) {
|
|
return false;
|
|
}
|
|
RAPIDJSON_NAMESPACE::Document& doc = j.jsonData.doc;
|
|
//doc.Parse(&v[0]);
|
|
|
|
return VRMIsVRM10(doc);
|
|
}
|
|
|
|
FString GetPluginThirdpartyPath() {
|
|
std::string ret;
|
|
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin("VRM4U");
|
|
if (!Plugin.IsValid())
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Plugin not found!"));
|
|
return FString();
|
|
}
|
|
return Plugin->GetBaseDir() / TEXT("Thirdparty");
|
|
}
|
|
|
|
std::string ReadTextFileFromThirdparty(const FString& RelativePath)
|
|
{
|
|
std::string ret;
|
|
|
|
FString TextFilePath = GetPluginThirdpartyPath() / RelativePath;
|
|
|
|
// ファイルの存在確認
|
|
if (!FPaths::FileExists(TextFilePath))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: no file: %s"), *TextFilePath);
|
|
return ret;
|
|
}
|
|
|
|
// テキストファイルを読み込む
|
|
FString FileContent;
|
|
TArray<uint8> data;
|
|
//if (FFileHelper::LoadFileToString(FileContent, *ThirdpartyPath))
|
|
if (FFileHelper::LoadFileToArray(data, *TextFilePath))
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: read: %s"), *TextFilePath);
|
|
return std::string(reinterpret_cast<const char*>(data.GetData()), data.Num());
|
|
} else
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: failed: %s"), *TextFilePath);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// Schema Provider ($ref をファイルパスで解決)
|
|
class FileSchemaProvider : public RAPIDJSON_NAMESPACE::IRemoteSchemaDocumentProvider {
|
|
public:
|
|
FileSchemaProvider(const std::string& baseDir){
|
|
BaseDir_ = FString(UTF8_TO_TCHAR(baseDir.c_str()));
|
|
}
|
|
|
|
const RAPIDJSON_NAMESPACE::SchemaDocument* GetRemoteDocument(const char* uri, RAPIDJSON_NAMESPACE::SizeType length) override {
|
|
// file name (ex: "VRM.meta.schema.json")
|
|
FString UriStr(UTF8_TO_TCHAR(uri));
|
|
FString SchemaFileName = FPaths::GetCleanFilename(UriStr);
|
|
|
|
// path (ex: /Game/Schemas/vrm0/VRM.meta.schema.json)
|
|
FString FilePath = FPaths::Combine(BaseDir_, SchemaFileName);
|
|
|
|
auto* CachedSchema = SchemaCache_.Find(FilePath);
|
|
if (CachedSchema) {
|
|
return *CachedSchema;
|
|
}
|
|
|
|
TArray<uint8> FileContent;
|
|
if (FPaths::FileExists(FilePath)) {
|
|
FFileHelper::LoadFileToArray(FileContent, *FilePath);
|
|
}
|
|
if (FileContent.Num() == 0){
|
|
if (FilePath.Contains(TEXT("/glTF"))) {
|
|
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: Failed to load schema: %s"), *FilePath);
|
|
} else {
|
|
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: Failed to load schema: %s"), *FilePath);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// to string
|
|
std::string SchemaStr(reinterpret_cast<const char*>(FileContent.GetData()), FileContent.Num());
|
|
|
|
|
|
// parse
|
|
auto Doc = std::make_unique<RAPIDJSON_NAMESPACE::Document>();
|
|
if (Doc->Parse(SchemaStr.c_str()).HasParseError()) {
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: parse error in %s: %s"), *FilePath,
|
|
UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(Doc->GetParseError())));
|
|
return nullptr;
|
|
}
|
|
|
|
// SchemaDocument
|
|
auto Schema = std::make_unique<RAPIDJSON_NAMESPACE::SchemaDocument>(*Doc, uri, length, this);
|
|
auto* Ptr = Schema.get();
|
|
SchemaCache_.Add(FilePath, Ptr);
|
|
DocCache_.Add(FilePath, std::move(Doc));
|
|
SchemaDocCache_.Add(FilePath, std::move(Schema));
|
|
return Ptr;
|
|
}
|
|
|
|
private:
|
|
FString BaseDir_;
|
|
TMap<FString, RAPIDJSON_NAMESPACE::SchemaDocument*> SchemaCache_;
|
|
TMap<FString, std::unique_ptr<RAPIDJSON_NAMESPACE::Document>> DocCache_;
|
|
TMap<FString, std::unique_ptr<RAPIDJSON_NAMESPACE::SchemaDocument>> SchemaDocCache_;
|
|
};
|
|
|
|
|
|
// VRM1
|
|
bool validateSchemaVRM1_internal(RAPIDJSON_NAMESPACE::Document& jsonDoc, const std::string& path, const std::string& fileExt) {
|
|
|
|
// ext check
|
|
// extなしなら通過
|
|
if (!jsonDoc.HasMember("extensions") || !jsonDoc["extensions"].HasMember(fileExt.c_str())) {
|
|
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: no extensions/%s"), UTF8_TO_TCHAR(fileExt.c_str()));
|
|
// ここは成功扱い
|
|
return true;
|
|
}
|
|
|
|
std::string filename = fileExt + ".schema.json";
|
|
std::string schemaStr = ReadTextFileFromThirdparty(FString(UTF8_TO_TCHAR((path + "/" + filename).c_str())));
|
|
|
|
// parse
|
|
RAPIDJSON_NAMESPACE::Document schemaDoc;
|
|
if (schemaDoc.Parse(schemaStr.c_str()).HasParseError()) {
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: validation failed at: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(jsonDoc.GetParseError())));
|
|
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
|
|
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
FileSchemaProvider provider(std::string(TCHAR_TO_UTF8(*(GetPluginThirdpartyPath() / UTF8_TO_TCHAR(path.c_str())))));
|
|
RAPIDJSON_NAMESPACE::SchemaDocument schema(schemaDoc, nullptr, 0, &provider);
|
|
RAPIDJSON_NAMESPACE::SchemaValidator validator(schema);
|
|
RAPIDJSON_NAMESPACE::Value& vrmExtension = jsonDoc["extensions"][fileExt.c_str()];
|
|
if (!vrmExtension.Accept(validator)) {
|
|
RAPIDJSON_NAMESPACE::StringBuffer sb;
|
|
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: Schema validation failed at: %s"), UTF8_TO_TCHAR(sb.GetString()));
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: nvalid keyword: %s"), UTF8_TO_TCHAR(validator.GetInvalidSchemaKeyword()));
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: valid %s"), UTF8_TO_TCHAR(fileExt.c_str()));
|
|
return true;
|
|
}
|
|
bool validateSchemaVRM1(RAPIDJSON_NAMESPACE::Document& jsonDoc) {
|
|
|
|
bool ret = true;
|
|
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_vrm-1.0/schema", "VRMC_vrm");
|
|
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_springBone-1.0/schema", "VRMC_springBone");
|
|
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_node_constraint-1.0/schema", "VRMC_node_constraint");
|
|
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_vrm_animation-1.0/schema", "VRMC_vrm_animation");
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// VRM0
|
|
bool validateSchemaVRM0_internal(RAPIDJSON_NAMESPACE::Document &jsonDoc, const std::string& path, const std::string& filename) {
|
|
|
|
std::string schemaStr = ReadTextFileFromThirdparty(FString(UTF8_TO_TCHAR((path + "/" + filename).c_str())));
|
|
|
|
// parse
|
|
RAPIDJSON_NAMESPACE::Document schemaDoc;
|
|
if (schemaDoc.Parse(schemaStr.c_str()).HasParseError()) {
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: validation failed at: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(jsonDoc.GetParseError())));
|
|
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
|
|
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// ext
|
|
if (!jsonDoc.HasMember("extensions") || !jsonDoc["extensions"].HasMember("VRM")) {
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: no extensions/vrm"));
|
|
//std::cerr << "Missing VRMC_vrm extension in JSON" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
FileSchemaProvider provider(std::string( TCHAR_TO_UTF8(*(GetPluginThirdpartyPath() / UTF8_TO_TCHAR(path.c_str())))) );
|
|
RAPIDJSON_NAMESPACE::SchemaDocument schema(schemaDoc, nullptr, 0, &provider);
|
|
RAPIDJSON_NAMESPACE::SchemaValidator validator(schema);
|
|
RAPIDJSON_NAMESPACE::Value& vrmExtension = jsonDoc["extensions"]["VRM"];
|
|
if (!vrmExtension.Accept(validator)) {
|
|
// エラー詳細を出力
|
|
RAPIDJSON_NAMESPACE::StringBuffer sb;
|
|
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: Schema validation failed at: %s"), UTF8_TO_TCHAR(sb.GetString()));
|
|
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: nvalid keyword: %s"), UTF8_TO_TCHAR(validator.GetInvalidSchemaKeyword()));
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: valid /%s"), UTF8_TO_TCHAR(filename.c_str()));
|
|
return true;
|
|
}
|
|
bool validateSchemaVRM0(RAPIDJSON_NAMESPACE::Document& jsonDoc) {
|
|
|
|
return validateSchemaVRM0_internal(jsonDoc, "vrm_specification/vrm0/schema", "vrm.schema.json");
|
|
}
|
|
|
|
|
|
bool VrmJson::validateSchema() {
|
|
bEnable = false;
|
|
|
|
std::vector<char> s;
|
|
//std::string ss = ReadTextFileFromThirdparty(TEXT("vrm_specification/vrm0/schema/vrm.schema.json"));
|
|
//std::string ss = ReadTextFileFromThirdparty(TEXT("vrm_specification/vrm1/VRMC_vrm-1.0/schema/VRMC_vrm.schema.json"));
|
|
|
|
if (VRMIsVRM10(doc)) {
|
|
if (validateSchemaVRM1(doc)) {
|
|
// vrm1 ok
|
|
bEnable = true;
|
|
} else {
|
|
// error
|
|
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: VRM1 validation failed"));
|
|
}
|
|
} else {
|
|
if (validateSchemaVRM0(doc)) {
|
|
bEnable = true;
|
|
// vrm0 ok
|
|
} else {
|
|
// error
|
|
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: VRM0 validation failed"));
|
|
}
|
|
}
|
|
|
|
if (bEnable == false) {
|
|
doc.Clear();
|
|
}
|
|
return bEnable;
|
|
}
|
|
|
|
bool VrmJson::init(const uint8_t* pData, size_t size) {
|
|
bEnable = false;
|
|
|
|
std::vector<char> v;
|
|
v.resize(size);
|
|
memcpy(&v[0], pData, size);
|
|
v.push_back(0);
|
|
|
|
|
|
if (doc.Parse(&v[0]).HasParseError()) {
|
|
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: parse error: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(doc.GetParseError())));
|
|
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
|
|
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
bEnable = true;
|
|
|
|
|
|
return true;
|
|
}
|
|
|