fix(core): resolve Obfuz player-build failure when HotUpdate.Code is referenced#635
Merged
JasonXuDeveloper merged 2 commits intomasterfrom Apr 23, 2026
Merged
Conversation
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 - 傑 <jason@xgamedev.net>
Unity Test Results✅ EditMode: All tests passed Unity Version: 2022.3.55f1 ✅ All tests passed! The PR is ready for review. View workflow run |
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/<target>/ - not in either.
Add ObfuzHotUpdateSearchPathInjector, an IPreprocessBuildWithReport /
IPostprocessBuildWithReport that snapshots
ObfuzSettings.additionalAssemblySearchPaths at the start of each
player build, appends HybridCLRData/HotUpdateDlls/<target>/ 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 - 傑 <jason@xgamedev.net>
9166633 to
b624d05
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FileNotFoundException: Assembly HotUpdate.Code not foundfrom Obfuz during Unity player build for users who have a compile-time reference toHotUpdate.Codeanywhere in Assembly-CSharp.autoReferencedtofalseon the template'sHotUpdate.Code.asmdef(removes the trigger by construction), plus anIPreprocessBuildWithReport/IPostprocessBuildWithReportthat injectsHybridCLRData/HotUpdateDlls/<target>/intoObfuzSettings.additionalAssemblySearchPathsonly during the build (defensive catch-all for users who deviate from the template).Obfuz.asseton disk; mutation is in-memory and reverted post-build.Why not just disable Obfuz
The workaround circulating in reports ("set Obfuz off") silently disables the entire player-DLL obfuscation stage - shipping
Assembly-CSharp,JEngine.Core, andObfuz.Generatedunobfuscated. That trades a real security feature for a clean build log. This PR keeps obfuscation on.Root cause
ObfuscationProcess.OnPostBuildPlayerScriptDLLs(com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs, callbackOrder 10000) obfuscates the player's managed DLLs. It builds anObfuscatorwhoseAssemblyCache.LoadModule(Editor/Utils/AssemblyCache.cs:75-97) recursively resolves everyGetAssemblyRefs()entry through a flatPathAssemblyResolver.FilterHotFixAssemblies(IFilterBuildAssemblies) correctly stripsHotUpdate.Codefrom the player staging area so it doesn't ship baked in.HotUpdate.Code.dllstill exists inHybridCLRData/HotUpdateDlls/<target>/from the prior code-build step - and Obfuz's resolver doesn't look there."autoReferenced": trueonHotUpdate.Code.asmdef, any Assembly-CSharp source that uses aHotUpdate.Code.*type embeds a real assembly ref into Assembly-CSharp. Obfuz then tries to load it during recursion and throws.The
assembly: HotUpdate.Code not found! ignore.line is a red herring - it'sTryLoadModulereturning null on the first pass. The actual throw comes from the subsequent strictLoadModulecall inAssemblyCache.LoadModule'sforeach (var refAsm in mod.GetAssemblyRefs())recursion.Changes
UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef-"autoReferenced": true->false. Hot-update assemblies should not be compile-referenced by Unity's predefined assemblies; main code loads them at runtime. Safe for the template:PromptInitializer.csreferences onlyJEngine.Core/JEngine.UI, and HybridCLR's ownAOTGenericReferences.csemits allHotUpdate.Code.*entries as//comments (GenericReferenceWriter.cs:72-127).UnityProject/Packages/com.jasonxudeveloper.jengine.core/Editor/CustomEditor/ObfuzHotUpdateSearchPathInjector.cs(new) - single class implementing both build-report hooks. SnapshotsObfuzSettings.additionalAssemblySearchPathspre-build, appendsSettingsUtil.GetHotUpdateDllsOutputDirByTarget(target)if present (warning log if missing, directing users to run the JEngine code-build step first), restores the original list post-build.Rebase-merge friendly: each commit is self-contained.
Test plan
Assets/Scripts/Ref.csdoingpublic static System.Type t = typeof(HotUpdate.Code.EntryPoint);, delete the injector, switch to Android, run Build Main Package (code), thenFile -> Build Settings -> Build. ExpectFileNotFoundException: Assembly HotUpdate.Code not foundandObfuscation failed.autoReferenced: trueand the throwaway ref file, restore the injector. Android build succeeds; console shows[JEngine] Injected HybridCLR hot-update dir into Obfuz search paths for this build: HybridCLRData/HotUpdateDlls/Android.Library/Obfuz/Android/ObfuscatedAssemblies/Assembly-CSharp.dllexists and is larger than the staging copy.autoReferenced: false, remove the throwaway ref file, remove the injector. Android build succeeds with no injection log (Assembly-CSharp no longer referencesHotUpdate.Code).HybridCLRData/HotUpdateDlls/Android/and build. Injector logs a clear warning naming the path and advising to run Build Main Package (code) first.StandaloneOSX, confirm the per-target path is used.ObfuzSettings.buildPipelineSettings.enable = 0, build succeeds (injector runs harmlessly, Obfuz exits early).ProjectSettings/Obfuz.assetadditionalAssemblySearchPathsis unchanged on disk after any build.HotUpdate.Code.dll.bytespresent under streaming assets, no rawHotUpdate.Code.dllassembly.No unit tests: core package convention is no editor tests (consistent with existing
BuildManager.cs), and the hook's inputs (BuildReport,ObfuzSettings.Instance,SettingsUtilstatic) aren't meaningfully mockable without a refactor that would add more surface area than the 10 lines of dedup-and-mutate logic being tested.