From d7c4f3048986427dc8ec2ec77bc91060f44154f1 Mon Sep 17 00:00:00 2001 From: deadYokai Date: Sun, 31 Dec 2023 06:36:17 +0200 Subject: [PATCH] Added Font Import/Export feature Fixed some std::format errors --- Launcher/CMakeLists.txt | 2 +- ModInstaller/CMakeLists.txt | 2 +- Plugins/CMakeLists.txt | 1 + Plugins/Font/CMakeLists.txt | 7 + Plugins/Font/Font.cpp | 338 +++++++++++++++++++++++++++++++ Plugins/Font/Font.def | 3 + UABE_Generic/AssetPluginUtil.cpp | 4 +- UABE_Generic/FileContextInfo.h | 1 + UABE_Win32/CMakeLists.txt | 2 +- 9 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 Plugins/Font/CMakeLists.txt create mode 100644 Plugins/Font/Font.cpp create mode 100644 Plugins/Font/Font.def diff --git a/Launcher/CMakeLists.txt b/Launcher/CMakeLists.txt index 4480423..693ad40 100644 --- a/Launcher/CMakeLists.txt +++ b/Launcher/CMakeLists.txt @@ -1,6 +1,6 @@ set(WIN32_EXECUTABLE TRUE) -add_executable (AssetBundleExtractor WIN32 "AssetBundleExtractor.cpp" AssetBundleExtractor.manifest Launcher.rc "resource.h") +add_executable (AssetBundleExtractor WIN32 "AssetBundleExtractor.cpp" Launcher.rc "resource.h") target_link_libraries (AssetBundleExtractor LINK_PUBLIC UABE_Win32) diff --git a/ModInstaller/CMakeLists.txt b/ModInstaller/CMakeLists.txt index 79d5bd8..6b754d5 100644 --- a/ModInstaller/CMakeLists.txt +++ b/ModInstaller/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library (ModInstaller SHARED dllmain.cpp InstallDialog.cpp InstallerDataFormat.cpp MakeIconResource.cpp MakeInstaller.cpp ModInstaller.cpp stdafx.cpp ../UABE_Win32/FileDialog.cpp Dialogs.rc ModInstaller.manifest) +add_library (ModInstaller SHARED dllmain.cpp InstallDialog.cpp InstallerDataFormat.cpp MakeIconResource.cpp MakeInstaller.cpp ModInstaller.cpp stdafx.cpp ../UABE_Win32/FileDialog.cpp Dialogs.rc ) target_include_directories (ModInstaller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(ModInstaller PRIVATE MODINSTALLER_EXPORTS) diff --git a/Plugins/CMakeLists.txt b/Plugins/CMakeLists.txt index 2c9df98..94d1b83 100644 --- a/Plugins/CMakeLists.txt +++ b/Plugins/CMakeLists.txt @@ -2,4 +2,5 @@ add_subdirectory (Utility) add_subdirectory (TextAsset) add_subdirectory (AudioClip) add_subdirectory (Texture) +add_subdirectory (Font) add_subdirectory (Mesh) diff --git a/Plugins/Font/CMakeLists.txt b/Plugins/Font/CMakeLists.txt new file mode 100644 index 0000000..7956e56 --- /dev/null +++ b/Plugins/Font/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library (Font SHARED "Font.cpp" Font.def) +target_include_directories (Font PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_target_properties(Font PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(Font PUBLIC UABE_Generic AssetsTools libStringConverter) diff --git a/Plugins/Font/Font.cpp b/Plugins/Font/Font.cpp new file mode 100644 index 0000000..602e004 --- /dev/null +++ b/Plugins/Font/Font.cpp @@ -0,0 +1,338 @@ +#include "../UABE_Generic/PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +#include +#include + + +static bool SupportsElements(std::vector& elements) +{ + std::unordered_map textClassIDs; + for (size_t i = 0; i < elements.size(); i++) + { + if (elements[i].asset.pFile == nullptr) + return false; + AssetsFileContextInfo* pFile = elements[i].asset.pFile.get(); + auto classIDsit = textClassIDs.find(pFile); + int32_t FontClassID = -1; + if (classIDsit == textClassIDs.end()) + { + FontClassID = pFile->GetClassByName("Font"); + textClassIDs[pFile] = FontClassID; + } + else + FontClassID = classIDsit->second; + if (FontClassID == -1) + return false; + int32_t classId = elements[i].asset.getClassID(); + if (classId != FontClassID) + return false; + } + return true; +} + +static bool isOtf(AssetTypeByteArray* data1){ + auto data = data1->data; + return data[0] == 0x4f && data[1] == 0x54 && data[2] == 0x54 && data[3] == 0x4f; +} + +static void FreeByteBufCallback(void* buffer) +{ + if (buffer) + delete[](uint8_t*)buffer; +} + +class FontImportTask : public AssetImportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; +public: + FontImportTask(AppContext& appContext, + std::vector _assets, std::vector _importFilePaths, + bool stopOnError = false) + + : AssetImportTask(std::move(_assets), std::move(_importFilePaths), "Import Font", stopOnError), + appContext(appContext) + {} + + bool importAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset, + [](AssetTypeTemplateField& templateBase) { + for (uint32_t i = 0; i < templateBase.children.size(); i++) + { + if (templateBase.children[i].name == "m_FontData") + { + templateBase.children[i].valueType = ValueType_None; + for (uint32_t k = 0; k < templateBase.children[i].children.size(); k++) + { + if (templateBase.children[i].children[k].name == "Array") + { + templateBase.children[i].children[k].type = "TypelessData"; + break; + } + } + break; + } + } + } + ); + + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + AssetTypeValueField* dataArrayField = pBaseField->Get("m_FontData")->Get(0U); + if (dataArrayField->GetValue() == nullptr) + throw AssetUtilError("Empty data"); + + if(dataArrayField->GetValue()->GetType() != ValueType_ByteArray) { + throw AssetUtilError("Unexpected Font format."); + } + + AssetTypeByteArray* bytes = dataArrayField->GetValue()->AsByteArray(); + + std::unique_ptr pReader(Create_AssetsReaderFromFile(path.c_str(), true, RWOpenFlags_Immediately)); + if (pReader == nullptr) + throw AssetUtilError("Unable to read the file."); + + QWORD size = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the file."); + + if (size >= INT32_MAX) + throw AssetUtilError("The file is too large (should be below 2 GiB)."); + + std::unique_ptr buf(new uint8_t[(size_t)size]); + + if (pReader->Read(size, buf.get()) != size) + throw AssetUtilError("Unable to read the file."); + + AssetTypeByteArray byteArrayValue = {}; + byteArrayValue.data = buf.get(); + byteArrayValue.size = (uint32_t)size; + dataArrayField->GetValue()->Set(&byteArrayValue); + + QWORD outSize = pBaseField->GetByteSize(0); + if (outSize >= SIZE_MAX) + throw AssetUtilError("Import size out of range."); + + std::unique_ptr newDataBuf(new uint8_t[outSize]); + std::unique_ptr pTempWriter(Create_AssetsWriterToMemory(newDataBuf.get(), outSize)); + if (pTempWriter == nullptr) + throw AssetUtilError("Unexpected runtime error."); + + QWORD newByteSize = pBaseField->Write(pTempWriter.get(), 0, desc.asset.isBigEndian()); + + std::shared_ptr pReplacer(MakeAssetModifierFromMemory(0, desc.asset.pathID, + desc.asset.getClassID(), desc.asset.getMonoScriptID(), + newDataBuf.release(), (size_t)newByteSize, FreeByteBufCallback)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + + desc.asset.pFile->addReplacer(pReplacer, appContext); + + return true; + } +}; +class FontImportProvider : public IAssetOptionProviderGeneric +{ +public: + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + public: + Runner(AppContext& appContext, std::vector _selection) + : appContext(appContext), selection(std::move(_selection)) + {} + void operator()() + { + std::vector importLocations = appContext.QueryAssetImportLocation(selection, "", "\\.*", "*|Font file:"); + if (!importLocations.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(importLocations)); + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Import; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(selection)) + return nullptr; + optionName = "Import font"; + return std::make_unique(appContext, std::move(selection)); + } +}; + + +class FontExportTask : public AssetExportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; +public: + FontExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export Font", ".ttf", std::move(_baseDir), stopOnError), + appContext(appContext) + {} + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset, + [](AssetTypeTemplateField& templateBase) { + for (uint32_t i = 0; i < templateBase.children.size(); i++) + { + if (templateBase.children[i].name == "m_FontData") + { + templateBase.children[i].valueType = ValueType_None; + for (uint32_t k = 0; k < templateBase.children[i].children.size(); k++) + { + if (templateBase.children[i].children[k].name == "Array") + { + templateBase.children[i].children[k].type = "TypelessData"; + break; + } + } + break; + } + } + } + ); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + AssetTypeValueField* dataArrayField = pBaseField->Get("m_FontData")->Get(0U); + if (dataArrayField->GetValue() == nullptr) + throw AssetUtilError("Empty data"); + + if(dataArrayField->GetValue()->GetType() != ValueType_ByteArray) { + throw AssetUtilError("Unexpected Font format."); + } + + AssetTypeByteArray* bytes = dataArrayField->GetValue()->AsByteArray(); + + std::string ext = isOtf(bytes) ? ".otf" : ".ttf"; + std::string fullOutputPath = path + ext; + std::unique_ptr pWriter(Create_AssetsWriterToFile(fullOutputPath.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + QWORD size = bytes->size; + if (pWriter->Write(size, bytes->data) != size) + throw AssetUtilError("Unable to write data to the output file."); + + return true; + } +}; +class FontExportProvider : public IAssetOptionProviderGeneric +{ +public: + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + public: + Runner(AppContext& appContext, std::vector _selection) + : appContext(appContext), selection(std::move(_selection)) + {} + void operator()() + { + std::string exportLocation = appContext.QueryAssetExportLocation(selection, "", "*|Font file:"); + if (!exportLocation.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation)); + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(selection)) + return nullptr; + optionName = "Export font"; + return std::make_unique(appContext, std::move(selection)); + } +}; + +class FontPluginDesc : public IPluginDesc +{ + std::vector> pProviders; +public: + FontPluginDesc() + { + pProviders = { std::make_shared(), std::make_shared() }; + } + std::string getName() + { + return "Font"; + } + std::string getAuthor() + { + return "deadYokai"; + } + std::string getDescriptionText() + { + return "Export and import the content of Font assets."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new FontPluginDesc(); +} diff --git a/Plugins/Font/Font.def b/Plugins/Font/Font.def new file mode 100644 index 0000000..a03fce7 --- /dev/null +++ b/Plugins/Font/Font.def @@ -0,0 +1,3 @@ +LIBRARY Font +EXPORTS + GetUABEPluginDesc1 diff --git a/UABE_Generic/AssetPluginUtil.cpp b/UABE_Generic/AssetPluginUtil.cpp index 0e7bb52..e8b217c 100644 --- a/UABE_Generic/AssetPluginUtil.cpp +++ b/UABE_Generic/AssetPluginUtil.cpp @@ -683,7 +683,7 @@ void AssetExportJSONDumpTask::recursiveDumpAsset(IAssetsReader* pReader, AssetTy break; default: if (strValue[i] < 0x20) - std::format_to(std::back_inserter(lineBuf), "\\u{:04u}", strValue[i]); + std::vformat_to(std::back_inserter(lineBuf), "\\u{:04u}", std::make_format_args(strValue[i])); break; } if (!lineBuf.empty()) @@ -710,7 +710,7 @@ void AssetExportJSONDumpTask::recursiveDumpAsset(IAssetsReader* pReader, AssetTy if (i) pDumpWriter->Write(2, ", "); lineBuf.clear(); - std::format_to(std::back_inserter(lineBuf), "{}", pByteArray->data[i]); + std::vformat_to(std::back_inserter(lineBuf), "{}", std::make_format_args(pByteArray->data[i])); outputLineBuf(); } outputRawChars("]", 1); diff --git a/UABE_Generic/FileContextInfo.h b/UABE_Generic/FileContextInfo.h index 836cea0..8d5c79a 100644 --- a/UABE_Generic/FileContextInfo.h +++ b/UABE_Generic/FileContextInfo.h @@ -11,6 +11,7 @@ #include #include #include +#include //unique_ptr to a IAssetsReader with a deleter. typedef std::unique_ptr IAssetsReader_ptr; diff --git a/UABE_Win32/CMakeLists.txt b/UABE_Win32/CMakeLists.txt index 2aa9a9c..202235b 100644 --- a/UABE_Win32/CMakeLists.txt +++ b/UABE_Win32/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library (UABE_Win32 SHARED AddAssetDialog.cpp AssetDependDialog.cpp AssetListDialog.cpp AssetViewModifyDialog.cpp BatchImportDialog.cpp BundleDialog.cpp FileDialog.cpp MainWindow2.cpp ModInstallerEditor2.cpp ModPackageLoader.cpp MonoBehaviourManager.cpp ProgressDialog.cpp SelectClassDbDialog.cpp TypeDatabaseEditor.cpp TypeDbPackageEditor.cpp Win32AppContext.cpp Win32ModTreeDialogBase.cpp AssetBundleExtractor.rc UABE_Win32.manifest "Win32BatchImportDesc.h" "api.h" "Win32BatchImportDesc.cpp" "Win32TaskStatusTracker.cpp" "SplitterControlHandler.cpp" "Win32PluginManager.cpp") +add_library (UABE_Win32 SHARED AddAssetDialog.cpp AssetDependDialog.cpp AssetListDialog.cpp AssetViewModifyDialog.cpp BatchImportDialog.cpp BundleDialog.cpp FileDialog.cpp MainWindow2.cpp ModInstallerEditor2.cpp ModPackageLoader.cpp MonoBehaviourManager.cpp ProgressDialog.cpp SelectClassDbDialog.cpp TypeDatabaseEditor.cpp TypeDbPackageEditor.cpp Win32AppContext.cpp Win32ModTreeDialogBase.cpp AssetBundleExtractor.rc "Win32BatchImportDesc.h" "api.h" "Win32BatchImportDesc.cpp" "Win32TaskStatusTracker.cpp" "SplitterControlHandler.cpp" "Win32PluginManager.cpp") target_include_directories (UABE_Win32 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${MCTRL_INCLUDE_DIR}) set_target_properties(UABE_Win32 PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")