From 1c49ad69b2509ed73ec5eac11bbae163a4d64c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Thu, 23 Apr 2026 20:36:56 +1000 Subject: [PATCH 1/2] fix(core): disable autoReferenced on HotUpdate.Code asmdef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hot-update assemblies are loaded at runtime by Bootstrap; they should not be compile-referenced by Unity's predefined assemblies (Assembly-CSharp, Assembly-CSharp-firstpass). With autoReferenced=true, any Assembly-CSharp source that uses a HotUpdate.Code.* type embeds a real assembly reference into Assembly-CSharp's metadata. During the Unity player build, HybridCLR's FilterHotFixAssemblies (IFilterBuildAssemblies) correctly strips HotUpdate.Code from the player staging area. Obfuz's ObfuscationProcess (IPostBuildPlayerScriptDLLs) then tries to obfuscate Assembly-CSharp and recursively resolves its assembly refs via AssemblyCache.LoadModule (Obfuz Editor/Utils/AssemblyCache.cs:75-97). The HotUpdate.Code ref can't be resolved from the player search paths, so obfuscation throws FileNotFoundException: Assembly HotUpdate.Code not found. Setting autoReferenced=false removes the trigger for template users: Assembly-CSharp no longer auto-references HotUpdate.Code, so nothing forces the metadata ref, and Obfuz's recursive resolver never asks for HotUpdate.Code. Verified safe for the current template: PromptInitializer references only JEngine.Core / JEngine.UI, and AOTGenericReferences.cs emits HotUpdate.Code.* entries as comments only (GenericReferenceWriter.cs:72-127). Signed-off-by: JasonXuDeveloper - 傑 --- UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef b/UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef index c55537455..606fdbd37 100644 --- a/UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef +++ b/UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef @@ -18,7 +18,7 @@ "allowUnsafeCode": false, "overrideReferences": false, "precompiledReferences": [], - "autoReferenced": true, + "autoReferenced": false, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false From b624d05c351b96d1b75cf83e451300aa926bab55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Thu, 23 Apr 2026 20:40:32 +1000 Subject: [PATCH 2/2] fix(core): inject HybridCLR hot-update dir into Obfuz player-build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Assembly-CSharp has a compile-time reference to HotUpdate.Code (e.g. because a user re-enables autoReferenced on the hot-update asmdef or their own code uses a HotUpdate.Code type), Unity's player build fails during Obfuz's ObfuscationProcess.OnPostBuildPlayerScriptDLLs with: FileNotFoundException: Assembly HotUpdate.Code not found at Obfuz.Utils.AssemblyCache.LoadModule (System.String moduleName) Root cause: HybridCLR's FilterHotFixAssemblies correctly strips HotUpdate.Code from the player staging area so it never ships baked in. But Obfuz then obfuscates Assembly-CSharp and recursively resolves every GetAssemblyRefs() entry via a flat PathAssemblyResolver. The resolver only searches the player staging dir plus ObfuzSettings.additionalAssemblySearchPaths, and HotUpdate.Code lives in HybridCLRData/HotUpdateDlls// - not in either. Add ObfuzHotUpdateSearchPathInjector, an IPreprocessBuildWithReport / IPostprocessBuildWithReport that snapshots ObfuzSettings.additionalAssemblySearchPaths at the start of each player build, appends HybridCLRData/HotUpdateDlls// when present, and restores the original list afterwards. In-memory only - Obfuz.asset on disk is never modified. The asmdef fix in the previous commit removes the trigger for the template; this processor is the defensive safety net for users who deviate from the template. Signed-off-by: JasonXuDeveloper - 傑 --- .../ObfuzHotUpdateSearchPathInjector.cs | 127 ++++++++++++++++++ .../ObfuzHotUpdateSearchPathInjector.cs.meta | 11 ++ .../Editor/Utilities/EditorUtilsTests.cs.meta | 11 ++ 3 files changed, 149 insertions(+) create mode 100644 UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs create mode 100644 UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs.meta create mode 100644 UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Tests/Editor/Utilities/EditorUtilsTests.cs.meta diff --git a/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs b/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs new file mode 100644 index 000000000..1ca1e6fc1 --- /dev/null +++ b/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs @@ -0,0 +1,127 @@ +// ObfuzHotUpdateSearchPathInjector.cs +// +// Author: +// JasonXuDeveloper +// +// Copyright (c) 2025 JEngine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.IO; +using System.Linq; +using HybridCLR.Editor; +using Obfuz.Settings; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace JEngine.Core.Editor.CustomEditor +{ + /// + /// Makes the HybridCLR hot-update DLL output directory visible to Obfuz's + /// player-build obfuscation pass so Assembly-CSharp's reference to + /// HotUpdate.Code (when present) can be resolved. + /// + /// + /// + /// During a Unity player build, HybridCLR's FilterHotFixAssemblies + /// strips hot-update assemblies from the player staging area (correct — they + /// must not ship as baked code). Obfuz's + /// ObfuscationProcess.OnPostBuildPlayerScriptDLLs then obfuscates the + /// remaining player DLLs and, via AssemblyCache.LoadModule, recursively + /// resolves every GetAssemblyRefs() entry through a flat + /// PathAssemblyResolver. If any obfuscated player assembly references + /// HotUpdate.Code, the resolver can't find it and Obfuz throws + /// FileNotFoundException: Assembly HotUpdate.Code not found. + /// + /// + /// This processor adds HybridCLRData/HotUpdateDlls/<target>/ to + /// ObfuzSettings.additionalAssemblySearchPaths at the start of the + /// build (in-memory only — Obfuz.asset on disk is never modified) and + /// restores the original list afterwards. + /// + /// + internal sealed class ObfuzHotUpdateSearchPathInjector + : IPreprocessBuildWithReport, IPostprocessBuildWithReport + { + // Run before Obfuz's IPostBuildPlayerScriptDLLs (callbackOrder = 10000). + public int callbackOrder => 0; + + private string[] _originalSearchPaths; + private bool _injected; + + public void OnPreprocessBuild(BuildReport report) + { + AssemblySettings assemblySettings = ObfuzSettings.Instance.assemblySettings; + _originalSearchPaths = assemblySettings.additionalAssemblySearchPaths + ?? System.Array.Empty(); + _injected = true; + + string hotUpdateDir = + SettingsUtil.GetHotUpdateDllsOutputDirByTarget(report.summary.platform); + + if (string.IsNullOrEmpty(hotUpdateDir) || !Directory.Exists(hotUpdateDir)) + { + Debug.LogWarning( + $"[JEngine] HybridCLR hot-update directory not found: " + + $"'{hotUpdateDir}'. If the player build fails with " + + $"'Assembly HotUpdate.Code not found' during Obfuz obfuscation, " + + $"run 'Build Main Package (code)' from the JEngine Panel first " + + $"so HybridCLR compiles the hot-update assemblies for this target."); + return; + } + + if (_originalSearchPaths.Any(p => AreSamePath(p, hotUpdateDir))) + { + return; + } + + assemblySettings.additionalAssemblySearchPaths = + _originalSearchPaths.Concat(new[] { hotUpdateDir }).ToArray(); + + Debug.Log( + $"[JEngine] Injected HybridCLR hot-update dir into Obfuz search " + + $"paths for this build: {hotUpdateDir}"); + } + + public void OnPostprocessBuild(BuildReport report) + { + if (!_injected) return; + + ObfuzSettings.Instance.assemblySettings.additionalAssemblySearchPaths = + _originalSearchPaths; + _originalSearchPaths = null; + _injected = false; + } + + private static bool AreSamePath(string a, string b) + { + if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false; + try + { + return Path.GetFullPath(a).TrimEnd(Path.DirectorySeparatorChar) + == Path.GetFullPath(b).TrimEnd(Path.DirectorySeparatorChar); + } + catch + { + return false; + } + } + } +} diff --git a/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs.meta b/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs.meta new file mode 100644 index 000000000..cf55a577a --- /dev/null +++ b/UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 772fbac4208e84eb4bc1d0707ad26847 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Tests/Editor/Utilities/EditorUtilsTests.cs.meta b/UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Tests/Editor/Utilities/EditorUtilsTests.cs.meta new file mode 100644 index 000000000..732a874d4 --- /dev/null +++ b/UnityProject/Packages/com.jasonxudeveloper.jengine.ui/Tests/Editor/Utilities/EditorUtilsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16dcf5d383a9a4aa5962ab1505770150 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: