diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index ea2d14958b30..bf0cc4204eea 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; +using System.Text; using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; @@ -45,6 +47,17 @@ public partial class ExportPlugin : EditorExportPlugin } }, { "default_value", true } + }, + new Godot.Collections.Dictionary() + { + { + "option", new Godot.Collections.Dictionary() + { + { "name", "dotnet/embed_build_outputs" }, + { "type", (int)Variant.Type.Bool } + } + }, + { "default_value", false } } }; } @@ -146,6 +159,8 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, long } } + bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs"); + foreach (var arch in archs) { string ridOS = DetermineRuntimeIdentifierOS(platform); @@ -190,17 +205,44 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, long "Publish succeeded but project assembly not found in the output directory"); } - // Add to the exported project shared object list. + var manifest = new StringBuilder(); + // Add to the exported project shared object list or packed resources. foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - AddSharedObject(file, tags: null, - Path.Join(projectDataDirName, - Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file)))); + if (embedBuildResults) + { + var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file)); + var fileData = File.ReadAllBytes(file); + var hash = Convert.ToBase64String(SHA512.HashData(fileData)); + + manifest.Append($"{filePath}\t{hash}\n"); + + AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false); + } + else + { + AddSharedObject(file, tags: null, + Path.Join(projectDataDirName, + Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file)))); + } + } + + if (embedBuildResults) + { + var fileData = Encoding.Default.GetBytes(manifest.ToString()); + AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false); } } } + private string SanitizeSlashes(string path) + { + if (Path.DirectorySeparatorChar == '\\') + return path.Replace('\\', '/'); + return path; + } + private string DetermineRuntimeIdentifierOS(string platform) => OS.DotNetOSPlatformMap[platform]; diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 159cb91d1b65..279b5cfed2e9 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -141,19 +141,52 @@ class _GodotSharpDirs { #else // TOOLS_ENABLED String arch = Engine::get_singleton()->get_architecture_name(); String appname_safe = path::get_csharp_project_name(); - String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch); - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = exe_dir.path_join("data_Godot_" + arch); - } + String packed_path = "res://.godot/mono/publish/" + arch; + if (DirAccess::exists(packed_path)) { + // The dotnet publish data is packed in the pck/zip. + String data_dir_root = OS::get_singleton()->get_cache_path().path_join("data_" + appname_safe + "_" + arch); + bool has_data = false; + if (!has_data) { + // 1. Try to access the data directly. + String global_packed = ProjectSettings::get_singleton()->globalize_path(packed_path); + if (global_packed.is_absolute_path() && FileAccess::exists(global_packed.path_join(".dotnet-publish-manifest"))) { + data_dir_root = global_packed; + has_data = true; + } + } + if (!has_data) { + // 2. Check if the data was extracted before and is up-to-date. + String packed_manifest = packed_path.path_join(".dotnet-publish-manifest"); + String extracted_manifest = data_dir_root.path_join(".dotnet-publish-manifest"); + if (FileAccess::exists(packed_manifest) && FileAccess::exists(extracted_manifest)) { + if (FileAccess::get_file_as_bytes(packed_manifest) == FileAccess::get_file_as_bytes(extracted_manifest)) { + has_data = true; + } + } + } + if (!has_data) { + // 3. Extract the data to a temporary location to load from there. + Ref da = DirAccess::create_for_path(packed_path); + ERR_FAIL_NULL(da); + ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK); + } + api_assemblies_dir = data_dir_root; + } else { + // The dotnet publish data is in a directory next to the executable. + String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch); + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = exe_dir.path_join("data_Godot_" + arch); + } #ifdef MACOS_ENABLED - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch); - } - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = res_dir.path_join("data_Godot_" + arch); - } + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch); + } + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = res_dir.path_join("data_Godot_" + arch); + } #endif - api_assemblies_dir = data_dir_root; + api_assemblies_dir = data_dir_root; + } #endif }