From cb86d8868e26ff3f7d7c2f54ebf2a0a4e219e139 Mon Sep 17 00:00:00 2001 From: Alex-Rachel <574809918@qq.com> Date: Sat, 26 Jul 2025 08:10:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5obfuz->2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UnityProject/Assets/Launcher/Launcher.asmdef | 3 +- UnityProject/Assets/Obfuz.meta | 8 + .../GeneratedEncryptionVirtualMachine.cs | 4522 ++++++++ .../GeneratedEncryptionVirtualMachine.cs.meta | 11 + UnityProject/Assets/Obfuz/ObfuzInitialize.cs | 24 + .../Assets/Obfuz/ObfuzInitialize.cs.meta | 3 + UnityProject/Assets/Obfuz/SymbolObfus.meta | 8 + .../Obfuz/SymbolObfus/symbol-mapping.xml | 9389 +++++++++++++++++ .../Obfuz/SymbolObfus/symbol-mapping.xml.meta | 7 + UnityProject/Assets/Resources.meta | 8 + UnityProject/Assets/Resources/Obfuz.meta | 8 + .../Obfuz/defaultDynamicSecretKey.bytes | Bin 0 -> 1024 bytes .../Obfuz/defaultDynamicSecretKey.bytes.meta | 7 + .../Obfuz/defaultStaticSecretKey.bytes | Bin 0 -> 1024 bytes .../Obfuz/defaultStaticSecretKey.bytes.meta | 7 + UnityProject/Assets/Scenes/main.unity | 1 - .../Editor/HybridCLR/BuildDLLCommand.cs | 60 +- .../TEngine/Editor/TEngine.Editor.asmdef | 4 +- .../TEngine/Settings/Prefab/GameEntry.prefab | 46 + .../2019.4.40/Unity.IL2CPP-Mac.dll | Bin 0 -> 955904 bytes .../2019.4.40/Unity.IL2CPP-Win.dll | Bin 0 -> 954368 bytes .../Data~/NetStandard/netstandard2.0.dll | Bin 0 -> 84992 bytes .../Data~/NetStandard/netstandard2.1.dll | Bin 0 -> 90112 bytes .../Data~/Templates/AssemblyManifest.cpp.tpl | 12 + .../Data~/Templates/MethodBridge.cpp.tpl | 32 + .../Data~/Templates/UnityVersion.h.tpl | 6 + .../Data~/hybridclr_version.json | 39 + .../com.code-philosophy.hybridclr/Editor.meta | 8 + .../Editor/3rds.meta | 8 + .../Editor/3rds/7zip.meta | 8 + .../Editor/3rds/7zip/Common.meta | 8 + .../Editor/3rds/7zip/Common/CRC.cs | 55 + .../Editor/3rds/7zip/Common/CRC.cs.meta | 11 + .../3rds/7zip/Common/CommandLineParser.cs | 274 + .../7zip/Common/CommandLineParser.cs.meta | 11 + .../Editor/3rds/7zip/Common/InBuffer.cs | 72 + .../Editor/3rds/7zip/Common/InBuffer.cs.meta | 11 + .../Editor/3rds/7zip/Common/OutBuffer.cs | 47 + .../Editor/3rds/7zip/Common/OutBuffer.cs.meta | 11 + .../Editor/3rds/7zip/Compress.meta | 8 + .../Editor/3rds/7zip/Compress/LZ.meta | 8 + .../3rds/7zip/Compress/LZ/IMatchFinder.cs | 24 + .../7zip/Compress/LZ/IMatchFinder.cs.meta | 11 + .../Editor/3rds/7zip/Compress/LZ/LzBinTree.cs | 367 + .../3rds/7zip/Compress/LZ/LzBinTree.cs.meta | 11 + .../3rds/7zip/Compress/LZ/LzInWindow.cs | 132 + .../3rds/7zip/Compress/LZ/LzInWindow.cs.meta | 11 + .../3rds/7zip/Compress/LZ/LzOutWindow.cs | 110 + .../3rds/7zip/Compress/LZ/LzOutWindow.cs.meta | 11 + .../Editor/3rds/7zip/Compress/LZMA.meta | 8 + .../3rds/7zip/Compress/LZMA/LzmaBase.cs | 76 + .../3rds/7zip/Compress/LZMA/LzmaBase.cs.meta | 11 + .../3rds/7zip/Compress/LZMA/LzmaDecoder.cs | 398 + .../7zip/Compress/LZMA/LzmaDecoder.cs.meta | 11 + .../3rds/7zip/Compress/LZMA/LzmaEncoder.cs | 1480 +++ .../7zip/Compress/LZMA/LzmaEncoder.cs.meta | 11 + .../Editor/3rds/7zip/Compress/RangeCoder.meta | 8 + .../7zip/Compress/RangeCoder/RangeCoder.cs | 234 + .../Compress/RangeCoder/RangeCoder.cs.meta | 11 + .../7zip/Compress/RangeCoder/RangeCoderBit.cs | 117 + .../Compress/RangeCoder/RangeCoderBit.cs.meta | 11 + .../Compress/RangeCoder/RangeCoderBitTree.cs | 157 + .../RangeCoder/RangeCoderBitTree.cs.meta | 11 + .../Editor/3rds/7zip/ICoder.cs | 157 + .../Editor/3rds/7zip/ICoder.cs.meta | 11 + .../Editor/3rds/UnityFS.meta | 8 + .../Editor/3rds/UnityFS/ArchiveFlags.cs | 14 + .../Editor/3rds/UnityFS/ArchiveFlags.cs.meta | 11 + .../3rds/UnityFS/BinaryReaderExtensions.cs | 51 + .../UnityFS/BinaryReaderExtensions.cs.meta | 11 + .../3rds/UnityFS/BinaryWriterExtensions.cs | 33 + .../UnityFS/BinaryWriterExtensions.cs.meta | 11 + .../Editor/3rds/UnityFS/BundleFileInfo.cs | 24 + .../3rds/UnityFS/BundleFileInfo.cs.meta | 11 + .../Editor/3rds/UnityFS/BundleFileReader.cs | 212 + .../3rds/UnityFS/BundleFileReader.cs.meta | 11 + .../Editor/3rds/UnityFS/BundleFileWriter.cs | 112 + .../3rds/UnityFS/BundleFileWriter.cs.meta | 11 + .../Editor/3rds/UnityFS/CompressionType.cs | 11 + .../3rds/UnityFS/CompressionType.cs.meta | 11 + .../Editor/3rds/UnityFS/EndianBinaryReader.cs | 107 + .../3rds/UnityFS/EndianBinaryReader.cs.meta | 11 + .../Editor/3rds/UnityFS/EndianBinaryWriter.cs | 107 + .../3rds/UnityFS/EndianBinaryWriter.cs.meta | 11 + .../Editor/3rds/UnityFS/EndianType.cs | 14 + .../Editor/3rds/UnityFS/EndianType.cs.meta | 11 + .../3rds/UnityFS/GlobalgamedatasPatcher.cs | 53 + .../UnityFS/GlobalgamedatasPatcher.cs.meta | 11 + .../Editor/3rds/UnityFS/Header.cs | 14 + .../Editor/3rds/UnityFS/Header.cs.meta | 11 + .../Editor/3rds/UnityFS/Node.cs | 10 + .../Editor/3rds/UnityFS/Node.cs.meta | 11 + .../UnityFS/ScriptingAssembliesJsonPatcher.cs | 50 + .../ScriptingAssembliesJsonPatcher.cs.meta | 11 + .../Editor/3rds/UnityFS/SevenZipHelper.cs | 49 + .../3rds/UnityFS/SevenZipHelper.cs.meta | 11 + .../Editor/3rds/UnityFS/StorageBlock.cs | 9 + .../Editor/3rds/UnityFS/StorageBlock.cs.meta | 11 + .../Editor/3rds/UnityFS/StorageBlockFlags.cs | 11 + .../3rds/UnityFS/StorageBlockFlags.cs.meta | 11 + .../Editor/3rds/UnityFS/StreamExtensions.cs | 32 + .../3rds/UnityFS/StreamExtensions.cs.meta | 11 + .../Editor/3rds/UnityFS/StreamFile.cs | 11 + .../Editor/3rds/UnityFS/StreamFile.cs.meta | 11 + .../Editor/3rds/UnityFS/UnityBinFile.cs | 124 + .../Editor/3rds/UnityFS/UnityBinFile.cs.meta | 11 + .../3rds/UnityFS/UnityBinFileDefines.cs | 397 + .../3rds/UnityFS/UnityBinFileDefines.cs.meta | 11 + .../Editor/3rds/UnityFS/UnityBinUtils.cs | 78 + .../Editor/3rds/UnityFS/UnityBinUtils.cs.meta | 11 + .../Editor/3rds/UnityHook.meta | 8 + .../Editor/3rds/UnityHook/CodePatcher.cs | 320 + .../Editor/3rds/UnityHook/CodePatcher.cs.meta | 11 + .../Editor/3rds/UnityHook/HookPool.cs | 74 + .../Editor/3rds/UnityHook/HookPool.cs.meta | 11 + .../Editor/3rds/UnityHook/HookUtils.cs | 272 + .../Editor/3rds/UnityHook/HookUtils.cs.meta | 11 + .../Editor/3rds/UnityHook/HookUtils_OSX.cs | 116 + .../3rds/UnityHook/HookUtils_OSX.cs.meta | 11 + .../Editor/3rds/UnityHook/HybridCLRHooks.meta | 8 + .../CopyStrippedAOTAssembliesHook.cs | 67 + .../CopyStrippedAOTAssembliesHook.cs.meta | 11 + .../HybridCLRHooks/GetIl2CppFolderHook.cs | 56 + .../GetIl2CppFolderHook.cs.meta | 11 + .../PatchScriptingAssembliesJsonHook.cs | 74 + .../PatchScriptingAssembliesJsonHook.cs.meta | 11 + .../Editor/3rds/UnityHook/LDasm.cs | 903 ++ .../Editor/3rds/UnityHook/LDasm.cs.meta | 11 + .../Editor/3rds/UnityHook/MethodHook.cs | 381 + .../Editor/3rds/UnityHook/MethodHook.cs.meta | 11 + .../Editor/3rds/UnityHook/Plugins.meta | 8 + .../Editor/3rds/UnityHook/Plugins/Utils.cpp | 23 + .../3rds/UnityHook/Plugins/Utils.cpp.meta | 81 + .../build_libMonoHookUtils_OSX.dylib.sh | 4 + .../build_libMonoHookUtils_OSX.dylib.sh.meta | 7 + .../3rds/UnityHook/Plugins/silicon.meta | 8 + .../silicon/libMonoHookUtils_OSX.dylib | Bin 0 -> 33543 bytes .../silicon/libMonoHookUtils_OSX.dylib.meta | 81 + .../Editor/3rds/UnityHook/Plugins/x86_64.meta | 8 + .../Plugins/x86_64/libMonoHookUtils_OSX.dylib | Bin 0 -> 166928 bytes .../x86_64/libMonoHookUtils_OSX.dylib.meta | 81 + .../Editor/ABI.meta | 8 + .../Editor/ABI/ABIUtil.cs | 23 + .../Editor/ABI/ABIUtil.cs.meta | 11 + .../Editor/ABI/MethodDesc.cs | 77 + .../Editor/ABI/MethodDesc.cs.meta | 11 + .../Editor/ABI/ParamInfo.cs | 30 + .../Editor/ABI/ParamInfo.cs.meta | 11 + .../Editor/ABI/ParamOrReturnType.cs | 27 + .../Editor/ABI/ParamOrReturnType.cs.meta | 11 + .../Editor/ABI/PlatformABI.cs | 10 + .../Editor/ABI/PlatformABI.cs.meta | 11 + .../Editor/ABI/TypeCreator.cs | 102 + .../Editor/ABI/TypeCreator.cs.meta | 11 + .../Editor/ABI/TypeInfo.cs | 116 + .../Editor/ABI/TypeInfo.cs.meta | 11 + .../ABI/ValueTypeSizeAligmentCalculator.cs | 181 + .../ValueTypeSizeAligmentCalculator.cs.meta | 11 + .../Editor/AOT.meta | 8 + .../Editor/AOT/AOTAssemblyMetadataStripper.cs | 56 + .../AOT/AOTAssemblyMetadataStripper.cs.meta | 11 + .../Editor/AOT/Analyzer.cs | 231 + .../Editor/AOT/Analyzer.cs.meta | 11 + .../Editor/AOT/ConstraintContext.cs | 73 + .../Editor/AOT/ConstraintContext.cs.meta | 11 + .../Editor/AOT/GenericReferenceWriter.cs | 139 + .../Editor/AOT/GenericReferenceWriter.cs.meta | 11 + .../Editor/BuildProcessors.meta | 8 + .../AddLil2cppSourceCodeToXcodeproj2019.cs | 258 + ...ddLil2cppSourceCodeToXcodeproj2019.cs.meta | 11 + ...dLil2cppSourceCodeToXcodeproj2020Or2021.cs | 244 + ...cppSourceCodeToXcodeproj2020Or2021.cs.meta | 11 + ...Lil2cppSourceCodeToXcodeproj2022OrNewer.cs | 82 + ...ppSourceCodeToXcodeproj2022OrNewer.cs.meta | 11 + ...Lil2cppSourceCodeToXcodeproj2023OrNewer.cs | 34 + ...ppSourceCodeToXcodeproj2023OrNewer.cs.meta | 2 + .../BuildProcessors/BuildProcessorUtil.cs | 25 + .../BuildProcessorUtil.cs.meta | 11 + .../Editor/BuildProcessors/CheckSettings.cs | 94 + .../BuildProcessors/CheckSettings.cs.meta | 11 + .../CopyStrippedAOTAssemblies.cs | 134 + .../CopyStrippedAOTAssemblies.cs.meta | 11 + .../BuildProcessors/FilterHotFixAssemblies.cs | 68 + .../FilterHotFixAssemblies.cs.meta | 11 + .../BuildProcessors/MsvcStdextWorkaround.cs | 32 + .../MsvcStdextWorkaround.cs.meta | 11 + .../PatchScriptingAssemblyList.cs | 189 + .../PatchScriptingAssemblyList.cs.meta | 11 + .../ScriptingAssembliesJsonPatcher.cs | 50 + .../ScriptingAssembliesJsonPatcher.cs.meta | 11 + .../Editor/Commands.meta | 8 + .../Commands/AOTReferenceGeneratorCommand.cs | 132 + .../AOTReferenceGeneratorCommand.cs.meta | 11 + .../Editor/Commands/CompileDllCommand.cs | 101 + .../Editor/Commands/CompileDllCommand.cs.meta | 11 + .../Commands/Il2CppDefGeneratorCommand.cs | 33 + .../Il2CppDefGeneratorCommand.cs.meta | 11 + .../Editor/Commands/LinkGeneratorCommand.cs | 39 + .../Commands/LinkGeneratorCommand.cs.meta | 11 + .../Commands/MethodBridgeGeneratorCommand.cs | 98 + .../MethodBridgeGeneratorCommand.cs.meta | 11 + .../Editor/Commands/PrebuildCommand.cs | 39 + .../Editor/Commands/PrebuildCommand.cs.meta | 11 + .../Editor/Commands/StripAOTDllCommand.cs | 196 + .../Commands/StripAOTDllCommand.cs.meta | 11 + .../Editor/HashUtil.cs | 28 + .../Editor/HashUtil.cs.meta | 11 + .../Editor/HotUpdate.meta | 8 + .../HotUpdate/MissingMetadataChecker.cs | 109 + .../HotUpdate/MissingMetadataChecker.cs.meta | 11 + .../Editor/HybridCLR.Editor.asmdef | 18 + .../Editor/HybridCLR.Editor.asmdef.meta | 7 + .../Editor/Il2CppDef.meta | 8 + .../Editor/Il2CppDef/Il2CppDefGenerator.cs | 107 + .../Il2CppDef/Il2CppDefGenerator.cs.meta | 11 + .../Editor/Installer.meta | 8 + .../Editor/Installer/BashUtil.cs | 143 + .../Editor/Installer/BashUtil.cs.meta | 11 + .../Editor/Installer/InstallerController.cs | 308 + .../Installer/InstallerController.cs.meta | 11 + .../Editor/Installer/InstallerWindow.cs | 113 + .../Editor/Installer/InstallerWindow.cs.meta | 11 + .../Editor/Link.meta | 8 + .../Editor/Link/Analyzer.cs | 49 + .../Editor/Link/Analyzer.cs.meta | 11 + .../Editor/Link/LinkXmlWriter.cs | 53 + .../Editor/Link/LinkXmlWriter.cs.meta | 11 + .../Editor/Meta.meta | 8 + .../Editor/Meta/AssemblyCache.cs | 20 + .../Editor/Meta/AssemblyCache.cs.meta | 11 + .../Editor/Meta/AssemblyCacheBase.cs | 99 + .../Editor/Meta/AssemblyCacheBase.cs.meta | 11 + .../Meta/AssemblyReferenceDeepCollector.cs | 50 + .../AssemblyReferenceDeepCollector.cs.meta | 11 + .../Editor/Meta/AssemblyResolverBase.cs | 33 + .../Editor/Meta/AssemblyResolverBase.cs.meta | 11 + .../Editor/Meta/AssemblySorter.cs | 105 + .../Editor/Meta/AssemblySorter.cs.meta | 11 + .../Editor/Meta/CombinedAssemblyResolver.cs | 33 + .../Meta/CombinedAssemblyResolver.cs.meta | 11 + .../Editor/Meta/FixedSetAssemblyResolver.cs | 43 + .../Meta/FixedSetAssemblyResolver.cs.meta | 11 + .../Editor/Meta/GenericArgumentContext.cs | 110 + .../Meta/GenericArgumentContext.cs.meta | 11 + .../Editor/Meta/GenericClass.cs | 74 + .../Editor/Meta/GenericClass.cs.meta | 11 + .../Editor/Meta/GenericMethod.cs | 109 + .../Editor/Meta/GenericMethod.cs.meta | 11 + .../Editor/Meta/IAssemblyResolver.cs | 13 + .../Editor/Meta/IAssemblyResolver.cs.meta | 11 + .../Editor/Meta/MetaUtil.cs | 217 + .../Editor/Meta/MetaUtil.cs.meta | 11 + .../Editor/Meta/MethodReferenceAnalyzer.cs | 79 + .../Meta/MethodReferenceAnalyzer.cs.meta | 11 + .../Editor/Meta/PathAssemblyResolver.cs | 40 + .../Editor/Meta/PathAssemblyResolver.cs.meta | 11 + .../Editor/MethodBridge.meta | 8 + .../Editor/MethodBridge/Analyzer.cs | 205 + .../Editor/MethodBridge/Analyzer.cs.meta | 11 + .../CallNativeMethodSignatureInfo.cs | 9 + .../CallNativeMethodSignatureInfo.cs.meta | 11 + .../Editor/MethodBridge/CalliAnalyzer.cs | 65 + .../Editor/MethodBridge/CalliAnalyzer.cs.meta | 11 + .../Editor/MethodBridge/Generator.cs | 1288 +++ .../Editor/MethodBridge/Generator.cs.meta | 11 + .../MonoPInvokeCallbackAnalyzer.cs | 78 + .../MonoPInvokeCallbackAnalyzer.cs.meta | 11 + .../Editor/MethodBridge/PInvokeAnalyzer.cs | 50 + .../MethodBridge/PInvokeAnalyzer.cs.meta | 11 + .../Editor/ReversePInvokeWrap.meta | 8 + .../Editor/Settings.meta | 8 + .../Settings/HybridCLRSettingProvider.cs | 106 + .../Settings/HybridCLRSettingProvider.cs.meta | 11 + .../Editor/Settings/HybridCLRSettings.cs | 98 + .../Editor/Settings/HybridCLRSettings.cs.meta | 11 + .../Editor/Settings/MenuProvider.cs | 39 + .../Editor/Settings/MenuProvider.cs.meta | 11 + .../Editor/SettingsUtil.cs | 116 + .../Editor/SettingsUtil.cs.meta | 11 + .../Editor/Template.meta | 8 + .../Editor/Template/FileRegionReplace.cs | 74 + .../Editor/Template/FileRegionReplace.cs.meta | 11 + .../com.code-philosophy.hybridclr/LICENSE | 21 + .../LICENSE.meta | 7 + .../Plugins.meta | 8 + .../Plugins/LZ4.dll | Bin 0 -> 38912 bytes .../Plugins/LZ4.dll.meta | 87 + .../com.code-philosophy.hybridclr/README.md | 90 + .../README.md.meta | 7 + .../README_EN.md | 88 + .../README_EN.md.meta | 7 + .../RELEASELOG.md | 977 ++ .../RELEASELOG.md.meta | 7 + .../Runtime.meta | 8 + .../Runtime/HomologousImageMode.cs | 10 + .../Runtime/HomologousImageMode.cs.meta | 11 + .../Runtime/HybridCLR.Runtime.asmdef | 14 + .../Runtime/HybridCLR.Runtime.asmdef.meta | 7 + .../Runtime/LoadImageErrorCode.cs | 16 + .../Runtime/LoadImageErrorCode.cs.meta | 11 + ...eversePInvokeWrapperGenerationAttribute.cs | 19 + ...ePInvokeWrapperGenerationAttribute.cs.meta | 11 + .../Runtime/RuntimeApi.cs | 139 + .../Runtime/RuntimeApi.cs.meta | 11 + .../Runtime/RuntimeOptionId.cs | 12 + .../Runtime/RuntimeOptionId.cs.meta | 11 + .../package.json | 22 + .../package.json.meta | 7 + .../com.code-philosophy.obfuz/Editor.meta | 8 + .../Editor/Conf.meta | 8 + .../Conf/XmlAssemblyTypeMethodRuleParser.cs | 263 + .../XmlAssemblyTypeMethodRuleParser.cs.meta | 11 + .../Editor/Conf/XmlFieldRuleParser.cs | 203 + .../Editor/Conf/XmlFieldRuleParser.cs.meta | 11 + .../Editor/ConfigurablePassPolicy.cs | 544 + .../Editor/ConfigurablePassPolicy.cs.meta | 11 + .../Editor/ConstValues.cs | 34 + .../Editor/ConstValues.cs.meta | 11 + .../Editor/Data.meta | 8 + .../Editor/Data/ConstFieldAllocator.cs | 269 + .../Editor/Data/ConstFieldAllocator.cs.meta | 11 + .../Editor/Data/RvaDataAllocator.cs | 324 + .../Editor/Data/RvaDataAllocator.cs.meta | 11 + .../Editor/Emit.meta | 8 + .../Editor/Emit/BasicBlockCollection.cs | 310 + .../Editor/Emit/BasicBlockCollection.cs.meta | 11 + .../Editor/Emit/DefaultMetadataImporter.cs | 423 + .../Emit/DefaultMetadataImporter.cs.meta | 11 + .../Editor/Emit/EntityExtensions.cs | 15 + .../Editor/Emit/EntityExtensions.cs.meta | 11 + .../Editor/Emit/EvalStackCalculator.cs | 967 ++ .../Editor/Emit/EvalStackCalculator.cs.meta | 11 + .../Editor/Emit/GroupByModuleEntityManager.cs | 89 + .../Emit/GroupByModuleEntityManager.cs.meta | 11 + .../Editor/Emit/LocalVariableAllocator.cs | 74 + .../Emit/LocalVariableAllocator.cs.meta | 11 + .../Editor/EncryptionVM.meta | 8 + .../EncryptionInstructionWithOpCode.cs | 25 + .../EncryptionInstructionWithOpCode.cs.meta | 11 + .../EncryptionVM/IEncryptionInstruction.cs | 24 + .../IEncryptionInstruction.cs.meta | 11 + .../Editor/EncryptionVM/Instructions.meta | 8 + .../Instructions/AddInstruction.cs | 35 + .../Instructions/AddInstruction.cs.meta | 11 + .../Instructions/AddRotateXorInstruction.cs | 62 + .../AddRotateXorInstruction.cs.meta | 11 + .../Instructions/AddXorRotateInstruction.cs | 62 + .../AddXorRotateInstruction.cs.meta | 11 + .../Instructions/BitRotateInstruction.cs | 46 + .../Instructions/BitRotateInstruction.cs.meta | 11 + .../Instructions/EncryptFunction.cs | 44 + .../Instructions/EncryptFunction.cs.meta | 11 + .../Instructions/MultipleInstruction.cs | 46 + .../Instructions/MultipleInstruction.cs.meta | 11 + .../MultipleRotateXorInstruction.cs | 65 + .../MultipleRotateXorInstruction.cs.meta | 11 + .../MultipleXorRotateInstruction.cs | 65 + .../MultipleXorRotateInstruction.cs.meta | 11 + .../Instructions/XorAddRotateInstruction.cs | 62 + .../XorAddRotateInstruction.cs.meta | 11 + .../Instructions/XorInstruction.cs | 36 + .../Instructions/XorInstruction.cs.meta | 11 + .../XorMultipleRotateInstruction.cs | 65 + .../XorMultipleRotateInstruction.cs.meta | 11 + .../Editor/EncryptionVM/VirtualMachine.cs | 17 + .../EncryptionVM/VirtualMachine.cs.meta | 11 + .../VirtualMachineCodeGenerator.cs | 203 + .../VirtualMachineCodeGenerator.cs.meta | 11 + .../EncryptionVM/VirtualMachineCreator.cs | 68 + .../VirtualMachineCreator.cs.meta | 11 + .../EncryptionVM/VirtualMachineSimulator.cs | 90 + .../VirtualMachineSimulator.cs.meta | 11 + .../Editor/GarbageCodeGeneration.meta | 8 + .../ConfigGarbageCodeGenerator.cs | 117 + .../ConfigGarbageCodeGenerator.cs.meta | 11 + .../GarbageCodeGenerator.cs | 92 + .../GarbageCodeGenerator.cs.meta | 11 + .../ISpecificGarbageCodeGenerator.cs | 22 + .../ISpecificGarbageCodeGenerator.cs.meta | 11 + .../SpecificGarbageCodeGeneratorBase.cs | 106 + .../SpecificGarbageCodeGeneratorBase.cs.meta | 11 + .../UIGarbageCodeGenerator.cs | 157 + .../UIGarbageCodeGenerator.cs.meta | 11 + .../Editor/IObfuscationPass.cs | 15 + .../Editor/IObfuscationPass.cs.meta | 11 + .../Editor/ObfusPasses.meta | 8 + .../BasicBlockObfuscationPassBase.cs | 53 + .../BasicBlockObfuscationPassBase.cs.meta | 11 + .../Editor/ObfusPasses/CallObfus.meta | 8 + .../ObfusPasses/CallObfus/CallObfusPass.cs | 166 + .../CallObfus/CallObfusPass.cs.meta | 11 + .../ConfigurableObfuscationPolicy.cs | 300 + .../ConfigurableObfuscationPolicy.cs.meta | 11 + .../CallObfus/DelegateProxyAllocator.cs | 263 + .../CallObfus/DelegateProxyAllocator.cs.meta | 11 + .../CallObfus/DelegateProxyObfuscator.cs | 83 + .../CallObfus/DelegateProxyObfuscator.cs.meta | 11 + .../CallObfus/DispatchProxyAllocator.cs | 273 + .../CallObfus/DispatchProxyAllocator.cs.meta | 11 + .../CallObfus/DispatchProxyObfuscator.cs | 53 + .../CallObfus/DispatchProxyObfuscator.cs.meta | 11 + .../CallObfus/IObfuscationPolicy.cs | 19 + .../CallObfus/IObfuscationPolicy.cs.meta | 11 + .../ObfusPasses/CallObfus/IObfuscator.cs | 20 + .../ObfusPasses/CallObfus/IObfuscator.cs.meta | 11 + .../Editor/ObfusPasses/CallObfus/MethodKey.cs | 30 + .../ObfusPasses/CallObfus/MethodKey.cs.meta | 11 + .../SpecialWhiteListMethodCalculator.cs | 122 + .../SpecialWhiteListMethodCalculator.cs.meta | 11 + .../Editor/ObfusPasses/CleanUp.meta | 8 + .../CleanUp/CleanUpInstructionPass.cs | 41 + .../CleanUp/CleanUpInstructionPass.cs.meta | 11 + .../CleanUp/RemoveObfuzAttributesPass.cs | 67 + .../CleanUp/RemoveObfuzAttributesPass.cs.meta | 11 + .../Editor/ObfusPasses/ConstEncrypt.meta | 8 + .../ConstEncrypt/ConfigurableEncryptPolicy.cs | 490 + .../ConfigurableEncryptPolicy.cs.meta | 11 + .../ConstEncrypt/ConstEncryptPass.cs | 137 + .../ConstEncrypt/ConstEncryptPass.cs.meta | 11 + .../ConstEncrypt/DefaultConstEncryptor.cs | 337 + .../DefaultConstEncryptor.cs.meta | 11 + .../ConstEncrypt/IConstEncryptor.cs | 32 + .../ConstEncrypt/IConstEncryptor.cs.meta | 11 + .../ConstEncrypt/IEncryptPolicy.cs | 43 + .../ConstEncrypt/IEncryptPolicy.cs.meta | 11 + .../Editor/ObfusPasses/ControlFlowObfus.meta | 8 + .../ConfigurableObfuscationPolicy.cs | 133 + .../ConfigurableObfuscationPolicy.cs.meta | 11 + .../ControlFlowObfus/ControlFlowObfusPass.cs | 80 + .../ControlFlowObfusPass.cs.meta | 11 + .../ControlFlowObfus/DefaultObfuscator.cs | 20 + .../DefaultObfuscator.cs.meta | 11 + .../ControlFlowObfus/IObfuscator.cs | 15 + .../ControlFlowObfus/IObfuscator.cs.meta | 11 + .../MethodControlFlowCalculator.cs | 894 ++ .../MethodControlFlowCalculator.cs.meta | 11 + .../Editor/ObfusPasses/EvalStackObfus.meta | 8 + .../ConfigurableObfuscationPolicy.cs | 147 + .../ConfigurableObfuscationPolicy.cs.meta | 11 + .../EvalStackObfus/DefaultObfuscator.cs | 225 + .../EvalStackObfus/DefaultObfuscator.cs.meta | 11 + .../EvalStackObfus/EvalStackObfusPass.cs | 114 + .../EvalStackObfus/EvalStackObfusPass.cs.meta | 11 + .../ObfusPasses/EvalStackObfus/IObfuscator.cs | 24 + .../EvalStackObfus/IObfuscator.cs.meta | 11 + .../Editor/ObfusPasses/ExprObfus.meta | 8 + .../ConfigurableObfuscationPolicy.cs | 143 + .../ConfigurableObfuscationPolicy.cs.meta | 11 + .../ObfusPasses/ExprObfus/ExprObfusPass.cs | 173 + .../ExprObfus/ExprObfusPass.cs.meta | 11 + .../ObfusPasses/ExprObfus/IObfuscator.cs | 28 + .../ObfusPasses/ExprObfus/IObfuscator.cs.meta | 11 + .../ObfusPasses/ExprObfus/Obfuscators.meta | 8 + .../Obfuscators/AdvancedObfuscator.cs | 110 + .../Obfuscators/AdvancedObfuscator.cs.meta | 11 + .../ExprObfus/Obfuscators/BasicObfuscator.cs | 282 + .../Obfuscators/BasicObfuscator.cs.meta | 11 + .../Obfuscators/MostAdvancedObfuscator.cs | 83 + .../MostAdvancedObfuscator.cs.meta | 11 + .../Editor/ObfusPasses/FieldEncrypt.meta | 8 + .../FieldEncrypt/ConfigurableEncryptPolicy.cs | 45 + .../ConfigurableEncryptPolicy.cs.meta | 11 + .../FieldEncrypt/DefaultFieldEncryptor.cs | 201 + .../DefaultFieldEncryptor.cs.meta | 11 + .../FieldEncrypt/FieldEncryptPass.cs | 102 + .../FieldEncrypt/FieldEncryptPass.cs.meta | 11 + .../FieldEncrypt/IEncryptPolicy.cs | 14 + .../FieldEncrypt/IEncryptPolicy.cs.meta | 11 + .../FieldEncrypt/IFieldEncryptor.cs | 26 + .../FieldEncrypt/IFieldEncryptor.cs.meta | 11 + .../Editor/ObfusPasses/Instinct.meta | 8 + .../ObfusPasses/Instinct/InstinctPass.cs | 138 + .../ObfusPasses/Instinct/InstinctPass.cs.meta | 11 + .../InstructionObfuscationPassBase.cs | 46 + .../InstructionObfuscationPassBase.cs.meta | 11 + .../ObfusPasses/ObfuscationMethodPassBase.cs | 47 + .../ObfuscationMethodPassBase.cs.meta | 11 + .../Editor/ObfusPasses/ObfuscationPassBase.cs | 18 + .../ObfusPasses/ObfuscationPassBase.cs.meta | 11 + .../Editor/ObfusPasses/ObfuscationPassType.cs | 26 + .../ObfusPasses/ObfuscationPassType.cs.meta | 11 + .../Editor/ObfusPasses/SymbolObfus.meta | 8 + .../ObfusPasses/SymbolObfus/INameMaker.cs | 37 + .../SymbolObfus/INameMaker.cs.meta | 11 + .../SymbolObfus/IObfuscationPolicy.cs | 17 + .../SymbolObfus/IObfuscationPolicy.cs.meta | 11 + .../ObfusPasses/SymbolObfus/NameMakers.meta | 8 + .../SymbolObfus/NameMakers/DebugNameMaker.cs | 31 + .../NameMakers/DebugNameMaker.cs.meta | 11 + .../SymbolObfus/NameMakers/INameScope.cs | 11 + .../SymbolObfus/NameMakers/INameScope.cs.meta | 11 + .../SymbolObfus/NameMakers/NameMakerBase.cs | 116 + .../NameMakers/NameMakerBase.cs.meta | 11 + .../NameMakers/NameMakerFactory.cs | 23 + .../NameMakers/NameMakerFactory.cs.meta | 11 + .../SymbolObfus/NameMakers/NameScope.cs | 41 + .../SymbolObfus/NameMakers/NameScope.cs.meta | 11 + .../SymbolObfus/NameMakers/NameScopeBase.cs | 63 + .../NameMakers/NameScopeBase.cs.meta | 11 + .../NameMakers/WordSetNameMaker.cs | 22 + .../NameMakers/WordSetNameMaker.cs.meta | 11 + .../ObfusPasses/SymbolObfus/Policies.meta | 8 + .../SymbolObfus/Policies/CacheRenamePolicy.cs | 67 + .../Policies/CacheRenamePolicy.cs.meta | 11 + .../Policies/CombineRenamePolicy.cs | 40 + .../Policies/CombineRenamePolicy.cs.meta | 11 + .../Policies/ConfigurableRenamePolicy.cs | 792 ++ .../Policies/ConfigurableRenamePolicy.cs.meta | 11 + .../Policies/ObfuscationPolicyBase.cs | 33 + .../Policies/ObfuscationPolicyBase.cs.meta | 11 + .../SymbolObfus/Policies/SupportPassPolicy.cs | 45 + .../Policies/SupportPassPolicy.cs.meta | 11 + .../Policies/SystemRenamePolicy.cs | 120 + .../Policies/SystemRenamePolicy.cs.meta | 11 + .../SymbolObfus/Policies/UnityRenamePolicy.cs | 264 + .../Policies/UnityRenamePolicy.cs.meta | 11 + .../ReflectionCompatibilityDetector.cs | 306 + .../ReflectionCompatibilityDetector.cs.meta | 11 + .../SymbolObfus/RenameRecordMap.cs | 742 ++ .../SymbolObfus/RenameRecordMap.cs.meta | 11 + .../SymbolObfus/SymbolObfusPass.cs | 31 + .../SymbolObfus/SymbolObfusPass.cs.meta | 11 + .../ObfusPasses/SymbolObfus/SymbolRename.cs | 864 ++ .../SymbolObfus/SymbolRename.cs.meta | 11 + .../VirtualMethodGroupCalculator.cs | 299 + .../VirtualMethodGroupCalculator.cs.meta | 11 + .../Editor/ObfuscationMethodWhitelist.cs | 110 + .../Editor/ObfuscationMethodWhitelist.cs.meta | 11 + .../Editor/ObfuscationPassContext.cs | 70 + .../Editor/ObfuscationPassContext.cs.meta | 11 + .../Editor/Obfuscator.cs | 352 + .../Editor/Obfuscator.cs.meta | 11 + .../Editor/ObfuscatorBuilder.cs | 207 + .../Editor/ObfuscatorBuilder.cs.meta | 11 + .../Editor/Obfuz.Editor.asmdef | 18 + .../Editor/Obfuz.Editor.asmdef.meta | 7 + .../Editor/Pipeline.cs | 47 + .../Editor/Pipeline.cs.meta | 11 + .../Editor/Settings.meta | 8 + .../Editor/Settings/AssemblySettings.cs | 41 + .../Editor/Settings/AssemblySettings.cs.meta | 11 + .../Editor/Settings/BuildPipelineSettings.cs | 18 + .../Settings/BuildPipelineSettings.cs.meta | 11 + .../Settings/CallObfuscationSettings.cs | 53 + .../Settings/CallObfuscationSettings.cs.meta | 11 + .../Settings/ConstEncryptionSettings.cs | 33 + .../Settings/ConstEncryptionSettings.cs.meta | 11 + .../ControlFlowObfuscationSettings.cs | 31 + .../ControlFlowObfuscationSettings.cs.meta | 11 + .../Editor/Settings/EncryptionVMSettings.cs | 18 + .../Settings/EncryptionVMSettings.cs.meta | 11 + .../Settings/EvalStackObfuscationSettings.cs | 27 + .../EvalStackObfuscationSettings.cs.meta | 11 + .../Settings/ExprObfuscationSettings.cs | 27 + .../Settings/ExprObfuscationSettings.cs.meta | 11 + .../Settings/FieldEncryptionSettings.cs | 33 + .../Settings/FieldEncryptionSettings.cs.meta | 11 + .../Settings/GarbageCodeGenerationSettings.cs | 41 + .../GarbageCodeGenerationSettings.cs.meta | 11 + .../Editor/Settings/ObfuscationLevel.cs | 10 + .../Editor/Settings/ObfuscationLevel.cs.meta | 11 + .../Settings/ObfuscationPassSettings.cs | 16 + .../Settings/ObfuscationPassSettings.cs.meta | 11 + .../Editor/Settings/ObfuzSettings.cs | 122 + .../Editor/Settings/ObfuzSettings.cs.meta | 11 + .../Editor/Settings/ObfuzSettingsProvider.cs | 123 + .../Settings/ObfuzSettingsProvider.cs.meta | 11 + .../Editor/Settings/PolymorphicDllSettings.cs | 18 + .../Settings/PolymorphicDllSettings.cs.meta | 11 + .../Editor/Settings/SecretSettings.cs | 28 + .../Editor/Settings/SecretSettings.cs.meta | 11 + .../Settings/SymbolObfuscationSettings.cs | 70 + .../SymbolObfuscationSettings.cs.meta | 11 + .../Editor/Unity.meta | 8 + .../Editor/Unity/LinkXmlProcess.cs | 149 + .../Editor/Unity/LinkXmlProcess.cs.meta | 11 + .../Editor/Unity/LiteSymbolMappingReader.cs | 116 + .../Unity/LiteSymbolMappingReader.cs.meta | 11 + .../Editor/Unity/ObfuscationBeginEventArgs.cs | 8 + .../Unity/ObfuscationBeginEventArgs.cs.meta | 11 + .../Editor/Unity/ObfuscationEndEventArgs.cs | 9 + .../Unity/ObfuscationEndEventArgs.cs.meta | 11 + .../Editor/Unity/ObfuscationProcess.cs | 144 + .../Editor/Unity/ObfuscationProcess.cs.meta | 11 + .../Editor/Unity/ObfuzMenu.cs | 88 + .../Editor/Unity/ObfuzMenu.cs.meta | 11 + .../UnityProjectManagedAssemblyResolver.cs | 57 + ...nityProjectManagedAssemblyResolver.cs.meta | 11 + .../Editor/Utils.meta | 8 + .../Editor/Utils/AssemblyCache.cs | 88 + .../Editor/Utils/AssemblyCache.cs.meta | 11 + .../Editor/Utils/AssemblyResolverBase.cs | 7 + .../Editor/Utils/AssemblyResolverBase.cs.meta | 11 + .../Editor/Utils/AssertUtil.cs | 28 + .../Editor/Utils/AssertUtil.cs.meta | 11 + .../Editor/Utils/BurstCompileComputeCache.cs | 90 + .../Utils/BurstCompileComputeCache.cs.meta | 11 + .../Editor/Utils/CachedDictionary.cs | 33 + .../Editor/Utils/CachedDictionary.cs.meta | 11 + .../Editor/Utils/CollectionExtensions.cs | 20 + .../Editor/Utils/CollectionExtensions.cs.meta | 11 + .../Editor/Utils/CombinedAssemblyResolver.cs | 33 + .../Utils/CombinedAssemblyResolver.cs.meta | 11 + .../Editor/Utils/ConfigUtil.cs | 78 + .../Editor/Utils/ConfigUtil.cs.meta | 11 + .../Editor/Utils/ConstObfusUtil.cs | 177 + .../Editor/Utils/ConstObfusUtil.cs.meta | 11 + .../Utils/DisableTypeDefFindCacheScope.cs | 21 + .../DisableTypeDefFindCacheScope.cs.meta | 11 + .../Editor/Utils/EncryptionUtil.cs | 45 + .../Editor/Utils/EncryptionUtil.cs.meta | 11 + .../Editor/Utils/FileUtil.cs | 96 + .../Editor/Utils/FileUtil.cs.meta | 11 + .../Editor/Utils/GenericArgumentContext.cs | 111 + .../Utils/GenericArgumentContext.cs.meta | 11 + .../Editor/Utils/HashUtil.cs | 67 + .../Editor/Utils/HashUtil.cs.meta | 11 + .../Editor/Utils/IAssemblyResolver.cs | 7 + .../Editor/Utils/IAssemblyResolver.cs.meta | 11 + .../Editor/Utils/IRandom.cs | 17 + .../Editor/Utils/IRandom.cs.meta | 11 + .../Editor/Utils/KeyGenerator.cs | 37 + .../Editor/Utils/KeyGenerator.cs.meta | 11 + .../Editor/Utils/MathUtil.cs | 58 + .../Editor/Utils/MathUtil.cs.meta | 11 + .../Editor/Utils/MetaUtil.cs | 922 ++ .../Editor/Utils/MetaUtil.cs.meta | 11 + .../Editor/Utils/NameMatcher.cs | 43 + .../Editor/Utils/NameMatcher.cs.meta | 11 + .../Editor/Utils/NumberRange.cs | 14 + .../Editor/Utils/NumberRange.cs.meta | 11 + .../Utils/ObfuzIgnoreScopeComputeCache.cs | 243 + .../ObfuzIgnoreScopeComputeCache.cs.meta | 11 + .../Editor/Utils/PathAssemblyResolver.cs | 28 + .../Editor/Utils/PathAssemblyResolver.cs.meta | 11 + .../Editor/Utils/PlatformUtil.cs | 13 + .../Editor/Utils/PlatformUtil.cs.meta | 11 + .../Editor/Utils/RandomUtil.cs | 19 + .../Editor/Utils/RandomUtil.cs.meta | 11 + .../Editor/Utils/RandomWithKey.cs | 63 + .../Editor/Utils/RandomWithKey.cs.meta | 11 + .../Editor/Utils/ReflectionUtil.cs | 31 + .../Editor/Utils/ReflectionUtil.cs.meta | 11 + .../Editor/Utils/ThisArgType.cs | 9 + .../Editor/Utils/ThisArgType.cs.meta | 11 + .../Editor/Utils/TypeSigUtil.cs | 219 + .../Editor/Utils/TypeSigUtil.cs.meta | 11 + .../com.code-philosophy.obfuz/LICENSE | 21 + .../com.code-philosophy.obfuz/LICENSE.meta | 7 + .../com.code-philosophy.obfuz/Plugins.meta | 8 + .../Plugins/dnlib.dll | Bin 0 -> 1263616 bytes .../Plugins/dnlib.dll.meta | 69 + .../com.code-philosophy.obfuz/README.md | 73 + .../com.code-philosophy.obfuz/README.md.meta | 7 + .../com.code-philosophy.obfuz/REAME-EN.md | 73 + .../REAME-EN.md.meta | 7 + .../com.code-philosophy.obfuz/Runtime.meta | 8 + .../Runtime/AssetUtility.cs | 15 + .../Runtime/AssetUtility.cs.meta | 11 + .../Runtime/ConstUtility.cs | 78 + .../Runtime/ConstUtility.cs.meta | 11 + .../Runtime/EncryptFieldAttribute.cs | 9 + .../Runtime/EncryptFieldAttribute.cs.meta | 11 + .../Runtime/EncryptionScope.cs | 31 + .../Runtime/EncryptionScope.cs.meta | 11 + .../Runtime/EncryptionService.cs | 127 + .../Runtime/EncryptionService.cs.meta | 11 + .../Runtime/EncryptorBase.cs | 362 + .../Runtime/EncryptorBase.cs.meta | 11 + .../Runtime/ExprUtility.cs | 247 + .../Runtime/ExprUtility.cs.meta | 11 + .../Runtime/IEncryptor.cs | 30 + .../Runtime/IEncryptor.cs.meta | 11 + .../Runtime/NullEncryptor.cs | 89 + .../Runtime/NullEncryptor.cs.meta | 11 + .../Runtime/ObfuscationInstincts.cs | 34 + .../Runtime/ObfuscationInstincts.cs.meta | 11 + .../Runtime/ObfuscationTypeMapper.cs | 72 + .../Runtime/ObfuscationTypeMapper.cs.meta | 11 + .../Runtime/Obfuz.Runtime.asmdef | 14 + .../Runtime/Obfuz.Runtime.asmdef.meta | 7 + .../Runtime/ObfuzIgnoreAttribute.cs | 20 + .../Runtime/ObfuzIgnoreAttribute.cs.meta | 11 + .../Runtime/ObfuzScope.cs | 24 + .../Runtime/ObfuzScope.cs.meta | 11 + .../com.code-philosophy.obfuz/package.json | 22 + .../package.json.meta | 7 + .../Editor.meta | 8 + .../Editor/ObfuscateUtil.cs | 118 + .../Editor/ObfuscateUtil.cs.meta | 11 + .../Editor/Obfuz4HybridCLR.asmdef | 19 + .../Editor/Obfuz4HybridCLR.asmdef.meta | 7 + .../Editor/Polymorphic.meta | 8 + .../Polymorphic/PolymorphicCodeGenerator.cs | 224 + .../PolymorphicCodeGenerator.cs.meta | 11 + .../Editor/Polymorphic/TableMetaInfos.cs | 300 + .../Editor/Polymorphic/TableMetaInfos.cs.meta | 11 + .../Editor/PrebuildCommandExt.cs | 164 + .../Editor/PrebuildCommandExt.cs.meta | 11 + .../LICENSE | 21 + .../LICENSE.meta | 7 + .../README.md | 2 + .../README.md.meta | 7 + .../Templates~/MetadataReader.h.tpl | 96 + .../Templates~/PolymorphicDatas.h.tpl | 88 + .../Templates~/PolymorphicDefs.h.tpl | 28 + .../Templates~/PolymorphicRawImage.cpp.tpl | 897 ++ .../Templates~/PolymorphicRawImage.h.tpl | 58 + .../package.json | 22 + .../package.json.meta | 7 + UnityProject/Packages/manifest.json | 1 - UnityProject/Packages/packages-lock.json | 19 +- UnityProject/ProjectSettings/Obfuz.asset | 85 + 713 files changed, 57092 insertions(+), 10 deletions(-) create mode 100644 UnityProject/Assets/Obfuz.meta create mode 100644 UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs create mode 100644 UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs.meta create mode 100644 UnityProject/Assets/Obfuz/ObfuzInitialize.cs create mode 100644 UnityProject/Assets/Obfuz/ObfuzInitialize.cs.meta create mode 100644 UnityProject/Assets/Obfuz/SymbolObfus.meta create mode 100644 UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml create mode 100644 UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml.meta create mode 100644 UnityProject/Assets/Resources.meta create mode 100644 UnityProject/Assets/Resources/Obfuz.meta create mode 100644 UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes create mode 100644 UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes.meta create mode 100644 UnityProject/Assets/Resources/Obfuz/defaultStaticSecretKey.bytes create mode 100644 UnityProject/Assets/Resources/Obfuz/defaultStaticSecretKey.bytes.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Mac.dll create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Win.dll create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.1.dll create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/AssemblyManifest.cpp.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/MethodBridge.cpp.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/UnityVersion.h.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Plugins.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Plugins/LZ4.dll create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Plugins/LZ4.dll.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/README.md create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/README.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/package.json create mode 100644 UnityProject/Packages/com.code-philosophy.hybridclr/package.json.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/LICENSE create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/LICENSE.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Plugins.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Plugins/dnlib.dll create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Plugins/dnlib.dll.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/README.md create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/README.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/REAME-EN.md create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/REAME-EN.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/AssetUtility.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/AssetUtility.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ConstUtility.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/package.json create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz/package.json.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md.meta create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/MetadataReader.h.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDatas.h.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDefs.h.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.cpp.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.h.tpl create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json create mode 100644 UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json.meta create mode 100644 UnityProject/ProjectSettings/Obfuz.asset diff --git a/UnityProject/Assets/Launcher/Launcher.asmdef b/UnityProject/Assets/Launcher/Launcher.asmdef index a8f49bd6..3945ac46 100644 --- a/UnityProject/Assets/Launcher/Launcher.asmdef +++ b/UnityProject/Assets/Launcher/Launcher.asmdef @@ -2,7 +2,8 @@ "name": "Launcher", "rootNamespace": "Launcher", "references": [ - "GUID:13ba8ce62aa80c74598530029cb2d649" + "GUID:13ba8ce62aa80c74598530029cb2d649", + "GUID:4140bd2e2764f1f47ab93125ecb61942" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/UnityProject/Assets/Obfuz.meta b/UnityProject/Assets/Obfuz.meta new file mode 100644 index 00000000..9c7662dd --- /dev/null +++ b/UnityProject/Assets/Obfuz.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2daec965c12426d4c81fd26f8cf380e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs b/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs new file mode 100644 index 00000000..90507fe5 --- /dev/null +++ b/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs @@ -0,0 +1,4522 @@ +/// This file is auto-generated by Obfuz. Do not modify it. +/// +/// Version: 0 +/// SecretKey: Obfuz +/// OpCodeCount: 256 + +namespace Obfuz.EncryptionVM +{ + public class GeneratedEncryptionVirtualMachine : Obfuz.EncryptorBase + { + + private const int kOpCodeBits = 8; + + private const int kOpCodeCount = 256; + + private const int kOpCodeMask = 255; + + + + private readonly int[] _secretKey; + + public GeneratedEncryptionVirtualMachine(byte[] secretKey) + { + this._secretKey = ConvertToIntKey(secretKey); + } + + public override int OpCodeCount => kOpCodeCount; + + public override int Encrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + uint revertOps = 0; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + revertOps <<= kOpCodeBits; + revertOps |= opCode; + uopts >>= kOpCodeBits; + } + + while (revertOps != 0) + { + uint opCode = revertOps & kOpCodeMask; + value = ExecuteEncrypt(value, (int)opCode, salt); + revertOps >>= kOpCodeBits; + } + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + value = ExecuteDecrypt(value, (int)opCode, salt); + uopts >>= kOpCodeBits; + } + return value; + } + + + private int ExecuteEncrypt(int value, int opCode, int salt) + { + switch (opCode) + { + case 0: + { + // MultipleInstruction + value = value * 598188269 + _secretKey[84] + salt; + return value; + } + case 1: + { + // MultipleRotateXorInstruction + value = value * 1350058129 + _secretKey[136]; + uint part1 = (uint)value << 4; + uint part2 = (uint)value >> (32 - 4); + value = (int)(part1 | part2); + value ^= 1817406469 ^ salt; + return value; + } + case 2: + { + // MultipleXorRotateInstruction + value = value * -1144218503 + _secretKey[246]; + value ^= -1498541961 ^ salt; + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = (int)(part1 | part2); + return value; + } + case 3: + { + // AddRotateXorInstruction + value += -1207833585 + _secretKey[26]; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + value ^= 29411710 ^ salt; + return value; + } + case 4: + { + // BitRotateInstruction + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = ((int)(part1 | part2) ^ _secretKey[85]) + salt; + return value; + } + case 5: + { + // MultipleXorRotateInstruction + value = value * -1447238259 + _secretKey[165]; + value ^= 86149918 ^ salt; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 6: + { + // AddRotateXorInstruction + value += -1856354856 + _secretKey[178]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + value ^= -1262500500 ^ salt; + return value; + } + case 7: + { + // XorInstruction + value = ((value ^ _secretKey[53]) + salt) ^ 665464645; + return value; + } + case 8: + { + // XorMultipleRotateInstruction + value ^= -1044567439 ^ salt; + value = value * -1860181607 + _secretKey[206]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + return value; + } + case 9: + { + // MultipleInstruction + value = value * 522878123 + _secretKey[196] + salt; + return value; + } + case 10: + { + // XorAddRotateInstruction + value ^= -755609206 ^ salt; + value += -1035239660 + _secretKey[199]; + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = (int)(part1 | part2); + return value; + } + case 11: + { + // AddInstruction + value = ((value + _secretKey[89]) ^ salt) + -1177184477; + return value; + } + case 12: + { + // BitRotateInstruction + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = ((int)(part1 | part2) ^ _secretKey[23]) + salt; + return value; + } + case 13: + { + // XorMultipleRotateInstruction + value ^= -1510419150 ^ salt; + value = value * -93709937 + _secretKey[68]; + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 14: + { + // MultipleRotateXorInstruction + value = value * 2015893433 + _secretKey[156]; + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = (int)(part1 | part2); + value ^= -1134639492 ^ salt; + return value; + } + case 15: + { + // BitRotateInstruction + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = ((int)(part1 | part2) ^ _secretKey[95]) + salt; + return value; + } + case 16: + { + // XorMultipleRotateInstruction + value ^= -759315565 ^ salt; + value = value * -436699251 + _secretKey[79]; + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = (int)(part1 | part2); + return value; + } + case 17: + { + // MultipleRotateXorInstruction + value = value * 1266530571 + _secretKey[75]; + uint part1 = (uint)value << 2; + uint part2 = (uint)value >> (32 - 2); + value = (int)(part1 | part2); + value ^= -3885258 ^ salt; + return value; + } + case 18: + { + // MultipleXorRotateInstruction + value = value * -259404705 + _secretKey[153]; + value ^= 1207613963 ^ salt; + uint part1 = (uint)value << 9; + uint part2 = (uint)value >> (32 - 9); + value = (int)(part1 | part2); + return value; + } + case 19: + { + // MultipleRotateXorInstruction + value = value * 1042624059 + _secretKey[86]; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + value ^= 792769043 ^ salt; + return value; + } + case 20: + { + // AddInstruction + value = ((value + _secretKey[194]) ^ salt) + -512520382; + return value; + } + case 21: + { + // XorInstruction + value = ((value ^ _secretKey[195]) + salt) ^ -1864951858; + return value; + } + case 22: + { + // BitRotateInstruction + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = ((int)(part1 | part2) ^ _secretKey[2]) + salt; + return value; + } + case 23: + { + // MultipleInstruction + value = value * -463118297 + _secretKey[203] + salt; + return value; + } + case 24: + { + // AddInstruction + value = ((value + _secretKey[110]) ^ salt) + 2026667919; + return value; + } + case 25: + { + // AddXorRotateInstruction + value += 848657810 + _secretKey[133]; + value ^= 392708821 ^ salt; + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 26: + { + // BitRotateInstruction + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = ((int)(part1 | part2) ^ _secretKey[93]) + salt; + return value; + } + case 27: + { + // XorAddRotateInstruction + value ^= 1294662302 ^ salt; + value += 1284012732 + _secretKey[58]; + uint part1 = (uint)value << 30; + uint part2 = (uint)value >> (32 - 30); + value = (int)(part1 | part2); + return value; + } + case 28: + { + // XorMultipleRotateInstruction + value ^= -1438081752 ^ salt; + value = value * -1171400509 + _secretKey[138]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + return value; + } + case 29: + { + // BitRotateInstruction + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = ((int)(part1 | part2) ^ _secretKey[240]) + salt; + return value; + } + case 30: + { + // MultipleRotateXorInstruction + value = value * 1892661727 + _secretKey[215]; + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = (int)(part1 | part2); + value ^= 1594594445 ^ salt; + return value; + } + case 31: + { + // BitRotateInstruction + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = ((int)(part1 | part2) ^ _secretKey[49]) + salt; + return value; + } + case 32: + { + // XorAddRotateInstruction + value ^= 226193183 ^ salt; + value += -1038657413 + _secretKey[247]; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + return value; + } + case 33: + { + // XorAddRotateInstruction + value ^= -484591087 ^ salt; + value += 459902223 + _secretKey[252]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + return value; + } + case 34: + { + // MultipleXorRotateInstruction + value = value * -1382643267 + _secretKey[211]; + value ^= 1186351980 ^ salt; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 35: + { + // XorAddRotateInstruction + value ^= 1658142493 ^ salt; + value += -283413931 + _secretKey[235]; + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 36: + { + // XorInstruction + value = ((value ^ _secretKey[90]) + salt) ^ -1892941953; + return value; + } + case 37: + { + // XorMultipleRotateInstruction + value ^= -1297440001 ^ salt; + value = value * -1166749617 + _secretKey[111]; + uint part1 = (uint)value << 31; + uint part2 = (uint)value >> (32 - 31); + value = (int)(part1 | part2); + return value; + } + case 38: + { + // MultipleRotateXorInstruction + value = value * -375439505 + _secretKey[213]; + uint part1 = (uint)value << 31; + uint part2 = (uint)value >> (32 - 31); + value = (int)(part1 | part2); + value ^= 1353158598 ^ salt; + return value; + } + case 39: + { + // XorAddRotateInstruction + value ^= -934836680 ^ salt; + value += 873171360 + _secretKey[72]; + uint part1 = (uint)value << 20; + uint part2 = (uint)value >> (32 - 20); + value = (int)(part1 | part2); + return value; + } + case 40: + { + // MultipleXorRotateInstruction + value = value * 1427441479 + _secretKey[140]; + value ^= -28088263 ^ salt; + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 41: + { + // AddRotateXorInstruction + value += 969234286 + _secretKey[116]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + value ^= -312111197 ^ salt; + return value; + } + case 42: + { + // MultipleInstruction + value = value * -1130955295 + _secretKey[29] + salt; + return value; + } + case 43: + { + // BitRotateInstruction + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = ((int)(part1 | part2) ^ _secretKey[1]) + salt; + return value; + } + case 44: + { + // BitRotateInstruction + uint part1 = (uint)value << 4; + uint part2 = (uint)value >> (32 - 4); + value = ((int)(part1 | part2) ^ _secretKey[184]) + salt; + return value; + } + case 45: + { + // MultipleXorRotateInstruction + value = value * -1782633617 + _secretKey[199]; + value ^= 1068062556 ^ salt; + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 46: + { + // XorMultipleRotateInstruction + value ^= 196245895 ^ salt; + value = value * -1852817781 + _secretKey[200]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + return value; + } + case 47: + { + // MultipleXorRotateInstruction + value = value * -50587953 + _secretKey[10]; + value ^= -1964107221 ^ salt; + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = (int)(part1 | part2); + return value; + } + case 48: + { + // MultipleXorRotateInstruction + value = value * 1216851115 + _secretKey[238]; + value ^= -38668552 ^ salt; + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 49: + { + // MultipleInstruction + value = value * -2133817615 + _secretKey[172] + salt; + return value; + } + case 50: + { + // AddXorRotateInstruction + value += 1412414820 + _secretKey[219]; + value ^= -2098495662 ^ salt; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + return value; + } + case 51: + { + // BitRotateInstruction + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = ((int)(part1 | part2) ^ _secretKey[97]) + salt; + return value; + } + case 52: + { + // MultipleRotateXorInstruction + value = value * -1791837593 + _secretKey[63]; + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = (int)(part1 | part2); + value ^= -21948406 ^ salt; + return value; + } + case 53: + { + // BitRotateInstruction + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = ((int)(part1 | part2) ^ _secretKey[221]) + salt; + return value; + } + case 54: + { + // MultipleInstruction + value = value * -1220403071 + _secretKey[96] + salt; + return value; + } + case 55: + { + // MultipleRotateXorInstruction + value = value * -867896207 + _secretKey[29]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + value ^= -1022882984 ^ salt; + return value; + } + case 56: + { + // XorMultipleRotateInstruction + value ^= 2122077674 ^ salt; + value = value * 957274637 + _secretKey[235]; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 57: + { + // AddRotateXorInstruction + value += -503448718 + _secretKey[175]; + uint part1 = (uint)value << 21; + uint part2 = (uint)value >> (32 - 21); + value = (int)(part1 | part2); + value ^= -720824840 ^ salt; + return value; + } + case 58: + { + // MultipleInstruction + value = value * -280528271 + _secretKey[231] + salt; + return value; + } + case 59: + { + // BitRotateInstruction + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = ((int)(part1 | part2) ^ _secretKey[75]) + salt; + return value; + } + case 60: + { + // MultipleRotateXorInstruction + value = value * -1403438719 + _secretKey[171]; + uint part1 = (uint)value << 27; + uint part2 = (uint)value >> (32 - 27); + value = (int)(part1 | part2); + value ^= 1420744071 ^ salt; + return value; + } + case 61: + { + // XorMultipleRotateInstruction + value ^= 1366253139 ^ salt; + value = value * -1583570963 + _secretKey[84]; + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 62: + { + // MultipleXorRotateInstruction + value = value * 24444809 + _secretKey[132]; + value ^= -1974823163 ^ salt; + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = (int)(part1 | part2); + return value; + } + case 63: + { + // BitRotateInstruction + uint part1 = (uint)value << 22; + uint part2 = (uint)value >> (32 - 22); + value = ((int)(part1 | part2) ^ _secretKey[119]) + salt; + return value; + } + case 64: + { + // XorInstruction + value = ((value ^ _secretKey[15]) + salt) ^ 1262347216; + return value; + } + case 65: + { + // AddInstruction + value = ((value + _secretKey[126]) ^ salt) + 1780280992; + return value; + } + case 66: + { + // MultipleXorRotateInstruction + value = value * -1871888237 + _secretKey[85]; + value ^= -1198162446 ^ salt; + uint part1 = (uint)value << 13; + uint part2 = (uint)value >> (32 - 13); + value = (int)(part1 | part2); + return value; + } + case 67: + { + // MultipleInstruction + value = value * 931252767 + _secretKey[128] + salt; + return value; + } + case 68: + { + // BitRotateInstruction + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = ((int)(part1 | part2) ^ _secretKey[178]) + salt; + return value; + } + case 69: + { + // XorInstruction + value = ((value ^ _secretKey[61]) + salt) ^ 1649427052; + return value; + } + case 70: + { + // MultipleRotateXorInstruction + value = value * -100486091 + _secretKey[83]; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + value ^= 849172121 ^ salt; + return value; + } + case 71: + { + // BitRotateInstruction + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = ((int)(part1 | part2) ^ _secretKey[169]) + salt; + return value; + } + case 72: + { + // XorAddRotateInstruction + value ^= 1068287172 ^ salt; + value += -2092062916 + _secretKey[138]; + uint part1 = (uint)value << 20; + uint part2 = (uint)value >> (32 - 20); + value = (int)(part1 | part2); + return value; + } + case 73: + { + // MultipleInstruction + value = value * -729679733 + _secretKey[34] + salt; + return value; + } + case 74: + { + // MultipleRotateXorInstruction + value = value * -1624925351 + _secretKey[30]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + value ^= -145743337 ^ salt; + return value; + } + case 75: + { + // MultipleRotateXorInstruction + value = value * -1768166349 + _secretKey[142]; + uint part1 = (uint)value << 4; + uint part2 = (uint)value >> (32 - 4); + value = (int)(part1 | part2); + value ^= 280941267 ^ salt; + return value; + } + case 76: + { + // MultipleInstruction + value = value * -1708700487 + _secretKey[156] + salt; + return value; + } + case 77: + { + // AddRotateXorInstruction + value += 314625916 + _secretKey[192]; + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = (int)(part1 | part2); + value ^= 827331935 ^ salt; + return value; + } + case 78: + { + // MultipleInstruction + value = value * 875189907 + _secretKey[141] + salt; + return value; + } + case 79: + { + // AddXorRotateInstruction + value += 1607953190 + _secretKey[133]; + value ^= -11549173 ^ salt; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + return value; + } + case 80: + { + // XorAddRotateInstruction + value ^= -1454482890 ^ salt; + value += 687186546 + _secretKey[95]; + uint part1 = (uint)value << 25; + uint part2 = (uint)value >> (32 - 25); + value = (int)(part1 | part2); + return value; + } + case 81: + { + // XorInstruction + value = ((value ^ _secretKey[195]) + salt) ^ 814860713; + return value; + } + case 82: + { + // AddRotateXorInstruction + value += 1998643542 + _secretKey[171]; + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = (int)(part1 | part2); + value ^= 213310246 ^ salt; + return value; + } + case 83: + { + // AddRotateXorInstruction + value += 542686146 + _secretKey[249]; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + value ^= -696314173 ^ salt; + return value; + } + case 84: + { + // AddRotateXorInstruction + value += 1734820207 + _secretKey[2]; + uint part1 = (uint)value << 9; + uint part2 = (uint)value >> (32 - 9); + value = (int)(part1 | part2); + value ^= 118718247 ^ salt; + return value; + } + case 85: + { + // XorInstruction + value = ((value ^ _secretKey[143]) + salt) ^ 1553710234; + return value; + } + case 86: + { + // AddRotateXorInstruction + value += -217984331 + _secretKey[146]; + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = (int)(part1 | part2); + value ^= -1402843691 ^ salt; + return value; + } + case 87: + { + // MultipleInstruction + value = value * 1078374119 + _secretKey[5] + salt; + return value; + } + case 88: + { + // MultipleRotateXorInstruction + value = value * -975647447 + _secretKey[158]; + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = (int)(part1 | part2); + value ^= -2044505542 ^ salt; + return value; + } + case 89: + { + // AddInstruction + value = ((value + _secretKey[40]) ^ salt) + -1213654475; + return value; + } + case 90: + { + // XorMultipleRotateInstruction + value ^= -1886972278 ^ salt; + value = value * 275510141 + _secretKey[206]; + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = (int)(part1 | part2); + return value; + } + case 91: + { + // AddRotateXorInstruction + value += -1724625239 + _secretKey[223]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + value ^= -979249928 ^ salt; + return value; + } + case 92: + { + // XorInstruction + value = ((value ^ _secretKey[83]) + salt) ^ -1104541704; + return value; + } + case 93: + { + // XorInstruction + value = ((value ^ _secretKey[31]) + salt) ^ 311150152; + return value; + } + case 94: + { + // XorMultipleRotateInstruction + value ^= 116496631 ^ salt; + value = value * 13120561 + _secretKey[176]; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + return value; + } + case 95: + { + // MultipleRotateXorInstruction + value = value * -1932552195 + _secretKey[87]; + uint part1 = (uint)value << 2; + uint part2 = (uint)value >> (32 - 2); + value = (int)(part1 | part2); + value ^= 2133438141 ^ salt; + return value; + } + case 96: + { + // MultipleRotateXorInstruction + value = value * 126641773 + _secretKey[174]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + value ^= 225535005 ^ salt; + return value; + } + case 97: + { + // MultipleRotateXorInstruction + value = value * -1013570837 + _secretKey[175]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + value ^= -1703839105 ^ salt; + return value; + } + case 98: + { + // XorAddRotateInstruction + value ^= -447564571 ^ salt; + value += -1783079937 + _secretKey[78]; + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 99: + { + // XorInstruction + value = ((value ^ _secretKey[111]) + salt) ^ -316631669; + return value; + } + case 100: + { + // XorMultipleRotateInstruction + value ^= -405694625 ^ salt; + value = value * 1711408839 + _secretKey[252]; + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = (int)(part1 | part2); + return value; + } + case 101: + { + // MultipleXorRotateInstruction + value = value * -910384311 + _secretKey[212]; + value ^= -1551058348 ^ salt; + uint part1 = (uint)value << 6; + uint part2 = (uint)value >> (32 - 6); + value = (int)(part1 | part2); + return value; + } + case 102: + { + // AddInstruction + value = ((value + _secretKey[18]) ^ salt) + -853736135; + return value; + } + case 103: + { + // AddInstruction + value = ((value + _secretKey[116]) ^ salt) + 1321376878; + return value; + } + case 104: + { + // MultipleRotateXorInstruction + value = value * 1039555235 + _secretKey[7]; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + value ^= 541697309 ^ salt; + return value; + } + case 105: + { + // AddRotateXorInstruction + value += -822594180 + _secretKey[1]; + uint part1 = (uint)value << 4; + uint part2 = (uint)value >> (32 - 4); + value = (int)(part1 | part2); + value ^= 1176608900 ^ salt; + return value; + } + case 106: + { + // AddInstruction + value = ((value + _secretKey[110]) ^ salt) + -882893600; + return value; + } + case 107: + { + // MultipleRotateXorInstruction + value = value * -346726819 + _secretKey[114]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + value ^= -1566546809 ^ salt; + return value; + } + case 108: + { + // MultipleInstruction + value = value * -981516343 + _secretKey[97] + salt; + return value; + } + case 109: + { + // XorAddRotateInstruction + value ^= -72794161 ^ salt; + value += 1161425930 + _secretKey[43]; + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = (int)(part1 | part2); + return value; + } + case 110: + { + // AddInstruction + value = ((value + _secretKey[238]) ^ salt) + -1619543125; + return value; + } + case 111: + { + // XorAddRotateInstruction + value ^= 1170459122 ^ salt; + value += -1766468683 + _secretKey[241]; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 112: + { + // AddXorRotateInstruction + value += 251687012 + _secretKey[219]; + value ^= 323019346 ^ salt; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + return value; + } + case 113: + { + // AddInstruction + value = ((value + _secretKey[97]) ^ salt) + -1049972438; + return value; + } + case 114: + { + // XorMultipleRotateInstruction + value ^= -492239002 ^ salt; + value = value * -1496840897 + _secretKey[216]; + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = (int)(part1 | part2); + return value; + } + case 115: + { + // AddInstruction + value = ((value + _secretKey[221]) ^ salt) + -748126329; + return value; + } + case 116: + { + // AddXorRotateInstruction + value += -441051263 + _secretKey[96]; + value ^= -5933889 ^ salt; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + return value; + } + case 117: + { + // MultipleRotateXorInstruction + value = value * -1850062787 + _secretKey[88]; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + value ^= 543895274 ^ salt; + return value; + } + case 118: + { + // MultipleInstruction + value = value * -747527445 + _secretKey[140] + salt; + return value; + } + case 119: + { + // BitRotateInstruction + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = ((int)(part1 | part2) ^ _secretKey[175]) + salt; + return value; + } + case 120: + { + // MultipleRotateXorInstruction + value = value * 345252089 + _secretKey[57]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + value ^= 1300544743 ^ salt; + return value; + } + case 121: + { + // XorAddRotateInstruction + value ^= -547380749 ^ salt; + value += 678597707 + _secretKey[119]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + return value; + } + case 122: + { + // XorMultipleRotateInstruction + value ^= 683601851 ^ salt; + value = value * -145367929 + _secretKey[191]; + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 123: + { + // BitRotateInstruction + uint part1 = (uint)value << 20; + uint part2 = (uint)value >> (32 - 20); + value = ((int)(part1 | part2) ^ _secretKey[15]) + salt; + return value; + } + case 124: + { + // BitRotateInstruction + uint part1 = (uint)value << 8; + uint part2 = (uint)value >> (32 - 8); + value = ((int)(part1 | part2) ^ _secretKey[132]) + salt; + return value; + } + case 125: + { + // MultipleInstruction + value = value * 1436085223 + _secretKey[120] + salt; + return value; + } + case 126: + { + // AddInstruction + value = ((value + _secretKey[197]) ^ salt) + -1374139785; + return value; + } + case 127: + { + // BitRotateInstruction + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = ((int)(part1 | part2) ^ _secretKey[26]) + salt; + return value; + } + case 128: + { + // XorAddRotateInstruction + value ^= 1415280510 ^ salt; + value += 723646816 + _secretKey[146]; + uint part1 = (uint)value << 21; + uint part2 = (uint)value >> (32 - 21); + value = (int)(part1 | part2); + return value; + } + case 129: + { + // AddRotateXorInstruction + value += 1410706317 + _secretKey[165]; + uint part1 = (uint)value << 30; + uint part2 = (uint)value >> (32 - 30); + value = (int)(part1 | part2); + value ^= 2132310656 ^ salt; + return value; + } + case 130: + { + // XorAddRotateInstruction + value ^= 1264971736 ^ salt; + value += -483221582 + _secretKey[93]; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 131: + { + // AddXorRotateInstruction + value += -1872731835 + _secretKey[53]; + value ^= 1531807059 ^ salt; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + return value; + } + case 132: + { + // AddXorRotateInstruction + value += 264114638 + _secretKey[240]; + value ^= 306666665 ^ salt; + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = (int)(part1 | part2); + return value; + } + case 133: + { + // AddRotateXorInstruction + value += 824712252 + _secretKey[138]; + uint part1 = (uint)value << 20; + uint part2 = (uint)value >> (32 - 20); + value = (int)(part1 | part2); + value ^= -1266414649 ^ salt; + return value; + } + case 134: + { + // AddRotateXorInstruction + value += -766337246 + _secretKey[35]; + uint part1 = (uint)value << 25; + uint part2 = (uint)value >> (32 - 25); + value = (int)(part1 | part2); + value ^= 2133737246 ^ salt; + return value; + } + case 135: + { + // AddXorRotateInstruction + value += -1997614825 + _secretKey[165]; + value ^= 1683555122 ^ salt; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 136: + { + // MultipleXorRotateInstruction + value = value * 278699987 + _secretKey[57]; + value ^= -1305042504 ^ salt; + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = (int)(part1 | part2); + return value; + } + case 137: + { + // BitRotateInstruction + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = ((int)(part1 | part2) ^ _secretKey[192]) + salt; + return value; + } + case 138: + { + // XorInstruction + value = ((value ^ _secretKey[135]) + salt) ^ 1715223135; + return value; + } + case 139: + { + // MultipleInstruction + value = value * 1809423757 + _secretKey[79] + salt; + return value; + } + case 140: + { + // AddRotateXorInstruction + value += -408450171 + _secretKey[11]; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + value ^= 806320034 ^ salt; + return value; + } + case 141: + { + // AddInstruction + value = ((value + _secretKey[95]) ^ salt) + -433941646; + return value; + } + case 142: + { + // AddXorRotateInstruction + value += 1775820811 + _secretKey[169]; + value ^= 2132471747 ^ salt; + uint part1 = (uint)value << 26; + uint part2 = (uint)value >> (32 - 26); + value = (int)(part1 | part2); + return value; + } + case 143: + { + // MultipleXorRotateInstruction + value = value * 457346731 + _secretKey[19]; + value ^= 114258470 ^ salt; + uint part1 = (uint)value << 2; + uint part2 = (uint)value >> (32 - 2); + value = (int)(part1 | part2); + return value; + } + case 144: + { + // AddRotateXorInstruction + value += -650723591 + _secretKey[206]; + uint part1 = (uint)value << 3; + uint part2 = (uint)value >> (32 - 3); + value = (int)(part1 | part2); + value ^= -1181275232 ^ salt; + return value; + } + case 145: + { + // MultipleInstruction + value = value * 1369495811 + _secretKey[169] + salt; + return value; + } + case 146: + { + // XorInstruction + value = ((value ^ _secretKey[154]) + salt) ^ -1297404981; + return value; + } + case 147: + { + // MultipleRotateXorInstruction + value = value * 787548271 + _secretKey[181]; + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = (int)(part1 | part2); + value ^= 801710213 ^ salt; + return value; + } + case 148: + { + // AddXorRotateInstruction + value += -1933121809 + _secretKey[230]; + value ^= 1566976773 ^ salt; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + return value; + } + case 149: + { + // BitRotateInstruction + uint part1 = (uint)value << 30; + uint part2 = (uint)value >> (32 - 30); + value = ((int)(part1 | part2) ^ _secretKey[188]) + salt; + return value; + } + case 150: + { + // BitRotateInstruction + uint part1 = (uint)value << 30; + uint part2 = (uint)value >> (32 - 30); + value = ((int)(part1 | part2) ^ _secretKey[53]) + salt; + return value; + } + case 151: + { + // AddInstruction + value = ((value + _secretKey[138]) ^ salt) + -2119615805; + return value; + } + case 152: + { + // MultipleRotateXorInstruction + value = value * 1289692111 + _secretKey[102]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + value ^= 221292457 ^ salt; + return value; + } + case 153: + { + // MultipleRotateXorInstruction + value = value * -414757417 + _secretKey[248]; + uint part1 = (uint)value << 13; + uint part2 = (uint)value >> (32 - 13); + value = (int)(part1 | part2); + value ^= 1486712056 ^ salt; + return value; + } + case 154: + { + // MultipleInstruction + value = value * 1625437745 + _secretKey[72] + salt; + return value; + } + case 155: + { + // MultipleRotateXorInstruction + value = value * 1500723835 + _secretKey[247]; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + value ^= -81016400 ^ salt; + return value; + } + case 156: + { + // MultipleRotateXorInstruction + value = value * -939493617 + _secretKey[252]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + value ^= -1187848798 ^ salt; + return value; + } + case 157: + { + // AddXorRotateInstruction + value += 605454035 + _secretKey[108]; + value ^= 1185916334 ^ salt; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + return value; + } + case 158: + { + // AddXorRotateInstruction + value += 2112611413 + _secretKey[235]; + value ^= -451761745 ^ salt; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + return value; + } + case 159: + { + // XorInstruction + value = ((value ^ _secretKey[229]) + salt) ^ 1660696922; + return value; + } + case 160: + { + // MultipleRotateXorInstruction + value = value * 1280312911 + _secretKey[111]; + uint part1 = (uint)value << 31; + uint part2 = (uint)value >> (32 - 31); + value = (int)(part1 | part2); + value ^= -431219573 ^ salt; + return value; + } + case 161: + { + // XorMultipleRotateInstruction + value ^= 1658933717 ^ salt; + value = value * 642349663 + _secretKey[198]; + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = (int)(part1 | part2); + return value; + } + case 162: + { + // AddRotateXorInstruction + value += -1278798944 + _secretKey[72]; + uint part1 = (uint)value << 20; + uint part2 = (uint)value >> (32 - 20); + value = (int)(part1 | part2); + value ^= 609336148 ^ salt; + return value; + } + case 163: + { + // MultipleXorRotateInstruction + value = value * -1217570675 + _secretKey[57]; + value ^= -1055021038 ^ salt; + uint part1 = (uint)value << 26; + uint part2 = (uint)value >> (32 - 26); + value = (int)(part1 | part2); + return value; + } + case 164: + { + // MultipleXorRotateInstruction + value = value * 1129428085 + _secretKey[225]; + value ^= 1389308323 ^ salt; + uint part1 = (uint)value << 7; + uint part2 = (uint)value >> (32 - 7); + value = (int)(part1 | part2); + return value; + } + case 165: + { + // XorAddRotateInstruction + value ^= 318043677 ^ salt; + value += -1939584600 + _secretKey[124]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + return value; + } + case 166: + { + // MultipleXorRotateInstruction + value = value * 601748357 + _secretKey[184]; + value ^= 2047590880 ^ salt; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 167: + { + // XorInstruction + value = ((value ^ _secretKey[114]) + salt) ^ -312123044; + return value; + } + case 168: + { + // AddXorRotateInstruction + value += 1897551751 + _secretKey[139]; + value ^= -1299860280 ^ salt; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + return value; + } + case 169: + { + // BitRotateInstruction + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = ((int)(part1 | part2) ^ _secretKey[10]) + salt; + return value; + } + case 170: + { + // MultipleInstruction + value = value * -1716044921 + _secretKey[60] + salt; + return value; + } + case 171: + { + // XorMultipleRotateInstruction + value ^= 820953326 ^ salt; + value = value * 1295924473 + _secretKey[242]; + uint part1 = (uint)value << 21; + uint part2 = (uint)value >> (32 - 21); + value = (int)(part1 | part2); + return value; + } + case 172: + { + // XorInstruction + value = ((value ^ _secretKey[89]) + salt) ^ 921116076; + return value; + } + case 173: + { + // AddInstruction + value = ((value + _secretKey[82]) ^ salt) + -1261901861; + return value; + } + case 174: + { + // MultipleRotateXorInstruction + value = value * 1029416329 + _secretKey[42]; + uint part1 = (uint)value << 1; + uint part2 = (uint)value >> (32 - 1); + value = (int)(part1 | part2); + value ^= -1758870671 ^ salt; + return value; + } + case 175: + { + // XorAddRotateInstruction + value ^= -99371457 ^ salt; + value += 1254595032 + _secretKey[10]; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 176: + { + // AddXorRotateInstruction + value += 1901168605 + _secretKey[131]; + value ^= -2036462975 ^ salt; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 177: + { + // MultipleRotateXorInstruction + value = value * 383500913 + _secretKey[29]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + value ^= -805817000 ^ salt; + return value; + } + case 178: + { + // AddXorRotateInstruction + value += 28490730 + _secretKey[13]; + value ^= 762578411 ^ salt; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 179: + { + // XorAddRotateInstruction + value ^= 1812019570 ^ salt; + value += -41308497 + _secretKey[85]; + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = (int)(part1 | part2); + return value; + } + case 180: + { + // XorInstruction + value = ((value ^ _secretKey[231]) + salt) ^ 1563652208; + return value; + } + case 181: + { + // AddRotateXorInstruction + value += -542613261 + _secretKey[75]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + value ^= -1148350591 ^ salt; + return value; + } + case 182: + { + // AddXorRotateInstruction + value += 1462569147 + _secretKey[135]; + value ^= 1842755263 ^ salt; + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 183: + { + // MultipleXorRotateInstruction + value = value * 934570325 + _secretKey[15]; + value ^= -159244912 ^ salt; + uint part1 = (uint)value << 8; + uint part2 = (uint)value >> (32 - 8); + value = (int)(part1 | part2); + return value; + } + case 184: + { + // BitRotateInstruction + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = ((int)(part1 | part2) ^ _secretKey[230]) + salt; + return value; + } + case 185: + { + // AddRotateXorInstruction + value += 45305078 + _secretKey[119]; + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = (int)(part1 | part2); + value ^= 1218428368 ^ salt; + return value; + } + case 186: + { + // AddXorRotateInstruction + value += 148564506 + _secretKey[160]; + value ^= 567100030 ^ salt; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 187: + { + // BitRotateInstruction + uint part1 = (uint)value << 21; + uint part2 = (uint)value >> (32 - 21); + value = ((int)(part1 | part2) ^ _secretKey[242]) + salt; + return value; + } + case 188: + { + // MultipleRotateXorInstruction + value = value * -1881635163 + _secretKey[30]; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + value ^= 730302816 ^ salt; + return value; + } + case 189: + { + // BitRotateInstruction + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = ((int)(part1 | part2) ^ _secretKey[93]) + salt; + return value; + } + case 190: + { + // BitRotateInstruction + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = ((int)(part1 | part2) ^ _secretKey[69]) + salt; + return value; + } + case 191: + { + // XorMultipleRotateInstruction + value ^= 1474104403 ^ salt; + value = value * 1888537457 + _secretKey[153]; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 192: + { + // AddInstruction + value = ((value + _secretKey[170]) ^ salt) + 1345231273; + return value; + } + case 193: + { + // BitRotateInstruction + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = ((int)(part1 | part2) ^ _secretKey[138]) + salt; + return value; + } + case 194: + { + // AddRotateXorInstruction + value += -1649692985 + _secretKey[138]; + uint part1 = (uint)value << 2; + uint part2 = (uint)value >> (32 - 2); + value = (int)(part1 | part2); + value ^= -1086752221 ^ salt; + return value; + } + case 195: + { + // XorMultipleRotateInstruction + value ^= 216360478 ^ salt; + value = value * 198928957 + _secretKey[23]; + uint part1 = (uint)value << 5; + uint part2 = (uint)value >> (32 - 5); + value = (int)(part1 | part2); + return value; + } + case 196: + { + // XorAddRotateInstruction + value ^= 1736125070 ^ salt; + value += -922639548 + _secretKey[211]; + uint part1 = (uint)value << 25; + uint part2 = (uint)value >> (32 - 25); + value = (int)(part1 | part2); + return value; + } + case 197: + { + // XorAddRotateInstruction + value ^= -1590872932 ^ salt; + value += -1729078426 + _secretKey[124]; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 198: + { + // AddXorRotateInstruction + value += -2086916257 + _secretKey[135]; + value ^= 2101329043 ^ salt; + uint part1 = (uint)value << 13; + uint part2 = (uint)value >> (32 - 13); + value = (int)(part1 | part2); + return value; + } + case 199: + { + // XorInstruction + value = ((value ^ _secretKey[133]) + salt) ^ -1188487898; + return value; + } + case 200: + { + // MultipleInstruction + value = value * -165216181 + _secretKey[162] + salt; + return value; + } + case 201: + { + // XorAddRotateInstruction + value ^= 2087683186 ^ salt; + value += -1882888353 + _secretKey[153]; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + return value; + } + case 202: + { + // MultipleRotateXorInstruction + value = value * -1941291837 + _secretKey[58]; + uint part1 = (uint)value << 22; + uint part2 = (uint)value >> (32 - 22); + value = (int)(part1 | part2); + value ^= -1855365205 ^ salt; + return value; + } + case 203: + { + // MultipleInstruction + value = value * 2036569383 + _secretKey[66] + salt; + return value; + } + case 204: + { + // MultipleXorRotateInstruction + value = value * 795577849 + _secretKey[206]; + value ^= 1668989123 ^ salt; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 205: + { + // MultipleInstruction + value = value * -1063887357 + _secretKey[169] + salt; + return value; + } + case 206: + { + // XorMultipleRotateInstruction + value ^= -387621173 ^ salt; + value = value * 413706907 + _secretKey[143]; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 207: + { + // XorInstruction + value = ((value ^ _secretKey[133]) + salt) ^ -1302837102; + return value; + } + case 208: + { + // XorMultipleRotateInstruction + value ^= 1201861103 ^ salt; + value = value * -1349002009 + _secretKey[5]; + uint part1 = (uint)value << 29; + uint part2 = (uint)value >> (32 - 29); + value = (int)(part1 | part2); + return value; + } + case 209: + { + // AddInstruction + value = ((value + _secretKey[188]) ^ salt) + -1698116194; + return value; + } + case 210: + { + // XorAddRotateInstruction + value ^= 955827838 ^ salt; + value += -5412811 + _secretKey[40]; + uint part1 = (uint)value << 3; + uint part2 = (uint)value >> (32 - 3); + value = (int)(part1 | part2); + return value; + } + case 211: + { + // MultipleXorRotateInstruction + value = value * 1209501053 + _secretKey[206]; + value ^= -261186202 ^ salt; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + return value; + } + case 212: + { + // XorInstruction + value = ((value ^ _secretKey[215]) + salt) ^ 1451245279; + return value; + } + case 213: + { + // AddInstruction + value = ((value + _secretKey[248]) ^ salt) + -48271475; + return value; + } + case 214: + { + // MultipleRotateXorInstruction + value = value * -685299407 + _secretKey[72]; + uint part1 = (uint)value << 31; + uint part2 = (uint)value >> (32 - 31); + value = (int)(part1 | part2); + value ^= 280704379 ^ salt; + return value; + } + case 215: + { + // MultipleRotateXorInstruction + value = value * 1188587057 + _secretKey[176]; + uint part1 = (uint)value << 17; + uint part2 = (uint)value >> (32 - 17); + value = (int)(part1 | part2); + value ^= -1507466225 ^ salt; + return value; + } + case 216: + { + // BitRotateInstruction + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = ((int)(part1 | part2) ^ _secretKey[162]) + salt; + return value; + } + case 217: + { + // XorInstruction + value = ((value ^ _secretKey[108]) + salt) ^ -1329546797; + return value; + } + case 218: + { + // XorAddRotateInstruction + value ^= 846489904 ^ salt; + value += 1710889501 + _secretKey[85]; + uint part1 = (uint)value << 11; + uint part2 = (uint)value >> (32 - 11); + value = (int)(part1 | part2); + return value; + } + case 219: + { + // XorInstruction + value = ((value ^ _secretKey[127]) + salt) ^ -339712479; + return value; + } + case 220: + { + // XorAddRotateInstruction + value ^= -1008587035 ^ salt; + value += -308188673 + _secretKey[78]; + uint part1 = (uint)value << 15; + uint part2 = (uint)value >> (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 221: + { + // MultipleInstruction + value = value * -2016434293 + _secretKey[111] + salt; + return value; + } + case 222: + { + // MultipleInstruction + value = value * -491329185 + _secretKey[198] + salt; + return value; + } + case 223: + { + // AddInstruction + value = ((value + _secretKey[160]) ^ salt) + -449129672; + return value; + } + case 224: + { + // MultipleXorRotateInstruction + value = value * -86469931 + _secretKey[84]; + value ^= -180027834 ^ salt; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 225: + { + // XorInstruction + value = ((value ^ _secretKey[58]) + salt) ^ 946019090; + return value; + } + case 226: + { + // AddRotateXorInstruction + value += 694016884 + _secretKey[225]; + uint part1 = (uint)value << 3; + uint part2 = (uint)value >> (32 - 3); + value = (int)(part1 | part2); + value ^= 1350981383 ^ salt; + return value; + } + case 227: + { + // AddRotateXorInstruction + value += -870643939 + _secretKey[168]; + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = (int)(part1 | part2); + value ^= 1680252929 ^ salt; + return value; + } + case 228: + { + // BitRotateInstruction + uint part1 = (uint)value << 4; + uint part2 = (uint)value >> (32 - 4); + value = ((int)(part1 | part2) ^ _secretKey[184]) + salt; + return value; + } + case 229: + { + // AddRotateXorInstruction + value += 1785715822 + _secretKey[199]; + uint part1 = (uint)value << 28; + uint part2 = (uint)value >> (32 - 28); + value = (int)(part1 | part2); + value ^= -1727043214 ^ salt; + return value; + } + case 230: + { + // XorInstruction + value = ((value ^ _secretKey[139]) + salt) ^ -1277148537; + return value; + } + case 231: + { + // MultipleXorRotateInstruction + value = value * 1298248033 + _secretKey[226]; + value ^= 1940873679 ^ salt; + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = (int)(part1 | part2); + return value; + } + case 232: + { + // XorMultipleRotateInstruction + value ^= -879839609 ^ salt; + value = value * 1286764861 + _secretKey[171]; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 233: + { + // AddInstruction + value = ((value + _secretKey[181]) ^ salt) + 328489970; + return value; + } + case 234: + { + // MultipleInstruction + value = value * -1393808723 + _secretKey[89] + salt; + return value; + } + case 235: + { + // XorAddRotateInstruction + value ^= 1290000091 ^ salt; + value += -1977097134 + _secretKey[203]; + uint part1 = (uint)value << 8; + uint part2 = (uint)value >> (32 - 8); + value = (int)(part1 | part2); + return value; + } + case 236: + { + // AddInstruction + value = ((value + _secretKey[113]) ^ salt) + 1890859361; + return value; + } + case 237: + { + // AddRotateXorInstruction + value += 1045620543 + _secretKey[216]; + uint part1 = (uint)value << 10; + uint part2 = (uint)value >> (32 - 10); + value = (int)(part1 | part2); + value ^= 1434413518 ^ salt; + return value; + } + case 238: + { + // AddXorRotateInstruction + value += -1706485027 + _secretKey[131]; + value ^= 1591345537 ^ salt; + uint part1 = (uint)value << 0; + uint part2 = (uint)value >> (32 - 0); + value = (int)(part1 | part2); + return value; + } + case 239: + { + // AddXorRotateInstruction + value += 1271081841 + _secretKey[29]; + value ^= 1117669949 ^ salt; + uint part1 = (uint)value << 24; + uint part2 = (uint)value >> (32 - 24); + value = (int)(part1 | part2); + return value; + } + case 240: + { + // XorMultipleRotateInstruction + value ^= -842525462 ^ salt; + value = value * 1426591501 + _secretKey[235]; + uint part1 = (uint)value << 12; + uint part2 = (uint)value >> (32 - 12); + value = (int)(part1 | part2); + return value; + } + case 241: + { + // AddInstruction + value = ((value + _secretKey[175]) ^ salt) + 1030822002; + return value; + } + case 242: + { + // MultipleRotateXorInstruction + value = value * 1176352505 + _secretKey[57]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + value ^= 811922151 ^ salt; + return value; + } + case 243: + { + // AddRotateXorInstruction + value += 656680947 + _secretKey[75]; + uint part1 = (uint)value << 23; + uint part2 = (uint)value >> (32 - 23); + value = (int)(part1 | part2); + value ^= -1258702719 ^ salt; + return value; + } + case 244: + { + // XorMultipleRotateInstruction + value ^= 90809787 ^ salt; + value = value * -1741148537 + _secretKey[191]; + uint part1 = (uint)value << 19; + uint part2 = (uint)value >> (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 245: + { + // AddRotateXorInstruction + value += -532913580 + _secretKey[15]; + uint part1 = (uint)value << 16; + uint part2 = (uint)value >> (32 - 16); + value = (int)(part1 | part2); + value ^= 99436168 ^ salt; + return value; + } + case 246: + { + // MultipleXorRotateInstruction + value = value * 1306804229 + _secretKey[230]; + value ^= 1471598712 ^ salt; + uint part1 = (uint)value << 22; + uint part2 = (uint)value >> (32 - 22); + value = (int)(part1 | part2); + return value; + } + case 247: + { + // XorMultipleRotateInstruction + value ^= 1941306053 ^ salt; + value = value * -939631919 + _secretKey[15]; + uint part1 = (uint)value << 26; + uint part2 = (uint)value >> (32 - 26); + value = (int)(part1 | part2); + return value; + } + case 248: + { + // AddRotateXorInstruction + value += 883137918 + _secretKey[96]; + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = (int)(part1 | part2); + value ^= 2045091157 ^ salt; + return value; + } + case 249: + { + // BitRotateInstruction + uint part1 = (uint)value << 13; + uint part2 = (uint)value >> (32 - 13); + value = ((int)(part1 | part2) ^ _secretKey[165]) + salt; + return value; + } + case 250: + { + // AddInstruction + value = ((value + _secretKey[96]) ^ salt) + -394947456; + return value; + } + case 251: + { + // BitRotateInstruction + uint part1 = (uint)value << 18; + uint part2 = (uint)value >> (32 - 18); + value = ((int)(part1 | part2) ^ _secretKey[93]) + salt; + return value; + } + case 252: + { + // AddInstruction + value = ((value + _secretKey[69]) ^ salt) + 1917332797; + return value; + } + case 253: + { + // AddXorRotateInstruction + value += 1006809939 + _secretKey[113]; + value ^= -1509317223 ^ salt; + uint part1 = (uint)value << 14; + uint part2 = (uint)value >> (32 - 14); + value = (int)(part1 | part2); + return value; + } + case 254: + { + // BitRotateInstruction + uint part1 = (uint)value << 9; + uint part2 = (uint)value >> (32 - 9); + value = ((int)(part1 | part2) ^ _secretKey[170]) + salt; + return value; + } + case 255: + { + // AddInstruction + value = ((value + _secretKey[138]) ^ salt) + 683715132; + return value; + } + + default: + throw new System.Exception($"Invalid opCode:{opCode}"); + } + } + + private int ExecuteDecrypt(int value, int opCode, int salt) + { + switch (opCode) + { + case 0: + { + // MultipleInstruction + value = (value - _secretKey[84] - salt) * -1954824987; + return value; + } + case 1: + { + // MultipleRotateXorInstruction + value ^= 1817406469 ^ salt; + uint value2 = (uint)value >> 4; + uint part1 = (uint)value << (32 - 4); + value = (int)(value2 | part1); + value = (value - _secretKey[136]) * -2114748303; + return value; + } + case 2: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 5; + uint part1 = (uint)value << (32 - 5); + value = (int)(value2 | part1); + value ^= -1498541961 ^ salt; + value = (value - _secretKey[246]) * -203485751; + return value; + } + case 3: + { + // AddRotateXorInstruction + value ^= 29411710 ^ salt; + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value -= -1207833585 + _secretKey[26]; + return value; + } + case 4: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[85]); + uint part1 = value2 >> 18; + uint part2 = value2 << (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 5: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value ^= 86149918 ^ salt; + value = (value - _secretKey[165]) * -327424699; + return value; + } + case 6: + { + // AddRotateXorInstruction + value ^= -1262500500 ^ salt; + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value -= -1856354856 + _secretKey[178]; + return value; + } + case 7: + { + // XorInstruction + value = ((value ^ 665464645) - salt) ^ _secretKey[53]; + return value; + } + case 8: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value = (value - _secretKey[206]) * -1476140375; + value ^= -1044567439 ^ salt; + return value; + } + case 9: + { + // MultipleInstruction + value = (value - _secretKey[196] - salt) * 2125307395; + return value; + } + case 10: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 10; + uint part1 = (uint)value << (32 - 10); + value = (int)(value2 | part1); + value -= -1035239660 + _secretKey[199]; + value ^= -755609206 ^ salt; + return value; + } + case 11: + { + // AddInstruction + value = ((value - -1177184477) ^ salt) - _secretKey[89]; + return value; + } + case 12: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[23]); + uint part1 = value2 >> 29; + uint part2 = value2 << (32 - 29); + value = (int)(part1 | part2); + return value; + } + case 13: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 19; + uint part1 = (uint)value << (32 - 19); + value = (int)(value2 | part1); + value = (value - _secretKey[68]) * 1170138479; + value ^= -1510419150 ^ salt; + return value; + } + case 14: + { + // MultipleRotateXorInstruction + value ^= -1134639492 ^ salt; + uint value2 = (uint)value >> 6; + uint part1 = (uint)value << (32 - 6); + value = (int)(value2 | part1); + value = (value - _secretKey[156]) * 275824265; + return value; + } + case 15: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[95]); + uint part1 = value2 >> 7; + uint part2 = value2 << (32 - 7); + value = (int)(part1 | part2); + return value; + } + case 16: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 6; + uint part1 = (uint)value << (32 - 6); + value = (int)(value2 | part1); + value = (value - _secretKey[79]) * 1552472901; + value ^= -759315565 ^ salt; + return value; + } + case 17: + { + // MultipleRotateXorInstruction + value ^= -3885258 ^ salt; + uint value2 = (uint)value >> 2; + uint part1 = (uint)value << (32 - 2); + value = (int)(value2 | part1); + value = (value - _secretKey[75]) * -1943909725; + return value; + } + case 18: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 9; + uint part1 = (uint)value << (32 - 9); + value = (int)(value2 | part1); + value ^= 1207613963 ^ salt; + value = (value - _secretKey[153]) * 1985974175; + return value; + } + case 19: + { + // MultipleRotateXorInstruction + value ^= 792769043 ^ salt; + uint value2 = (uint)value >> 11; + uint part1 = (uint)value << (32 - 11); + value = (int)(value2 | part1); + value = (value - _secretKey[86]) * 1412922099; + return value; + } + case 20: + { + // AddInstruction + value = ((value - -512520382) ^ salt) - _secretKey[194]; + return value; + } + case 21: + { + // XorInstruction + value = ((value ^ -1864951858) - salt) ^ _secretKey[195]; + return value; + } + case 22: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[2]); + uint part1 = value2 >> 15; + uint part2 = value2 << (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 23: + { + // MultipleInstruction + value = (value - _secretKey[203] - salt) * 891162519; + return value; + } + case 24: + { + // AddInstruction + value = ((value - 2026667919) ^ salt) - _secretKey[110]; + return value; + } + case 25: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 15; + uint part2 = (uint)value << (32 - 15); + value = (int)(part1 | part2); + value ^= 392708821 ^ salt; + value -= 848657810 + _secretKey[133]; + return value; + } + case 26: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[93]); + uint part1 = value2 >> 5; + uint part2 = value2 << (32 - 5); + value = (int)(part1 | part2); + return value; + } + case 27: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 30; + uint part1 = (uint)value << (32 - 30); + value = (int)(value2 | part1); + value -= 1284012732 + _secretKey[58]; + value ^= 1294662302 ^ salt; + return value; + } + case 28: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value = (value - _secretKey[138]) * -708470805; + value ^= -1438081752 ^ salt; + return value; + } + case 29: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[240]); + uint part1 = value2 >> 6; + uint part2 = value2 << (32 - 6); + value = (int)(part1 | part2); + return value; + } + case 30: + { + // MultipleRotateXorInstruction + value ^= 1594594445 ^ salt; + uint value2 = (uint)value >> 24; + uint part1 = (uint)value << (32 - 24); + value = (int)(value2 | part1); + value = (value - _secretKey[215]) * 1857241631; + return value; + } + case 31: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[49]); + uint part1 = value2 >> 19; + uint part2 = value2 << (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 32: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 17; + uint part1 = (uint)value << (32 - 17); + value = (int)(value2 | part1); + value -= -1038657413 + _secretKey[247]; + value ^= 226193183 ^ salt; + return value; + } + case 33: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value -= 459902223 + _secretKey[252]; + value ^= -484591087 ^ salt; + return value; + } + case 34: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value ^= 1186351980 ^ salt; + value = (value - _secretKey[211]) * 1159592341; + return value; + } + case 35: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 15; + uint part1 = (uint)value << (32 - 15); + value = (int)(value2 | part1); + value -= -283413931 + _secretKey[235]; + value ^= 1658142493 ^ salt; + return value; + } + case 36: + { + // XorInstruction + value = ((value ^ -1892941953) - salt) ^ _secretKey[90]; + return value; + } + case 37: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 31; + uint part1 = (uint)value << (32 - 31); + value = (int)(value2 | part1); + value = (value - _secretKey[111]) * -674216273; + value ^= -1297440001 ^ salt; + return value; + } + case 38: + { + // MultipleRotateXorInstruction + value ^= 1353158598 ^ salt; + uint value2 = (uint)value >> 31; + uint part1 = (uint)value << (32 - 31); + value = (int)(value2 | part1); + value = (value - _secretKey[213]) * 803536783; + return value; + } + case 39: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 20; + uint part1 = (uint)value << (32 - 20); + value = (int)(value2 | part1); + value -= 873171360 + _secretKey[72]; + value ^= -934836680 ^ salt; + return value; + } + case 40: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 18; + uint part1 = (uint)value << (32 - 18); + value = (int)(value2 | part1); + value ^= -28088263 ^ salt; + value = (value - _secretKey[140]) * 1881436791; + return value; + } + case 41: + { + // AddRotateXorInstruction + value ^= -312111197 ^ salt; + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value -= 969234286 + _secretKey[116]; + return value; + } + case 42: + { + // MultipleInstruction + value = (value - _secretKey[29] - salt) * 2102789665; + return value; + } + case 43: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[1]); + uint part1 = value2 >> 28; + uint part2 = value2 << (32 - 28); + value = (int)(part1 | part2); + return value; + } + case 44: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[184]); + uint part1 = value2 >> 4; + uint part2 = value2 << (32 - 4); + value = (int)(part1 | part2); + return value; + } + case 45: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 18; + uint part1 = (uint)value << (32 - 18); + value = (int)(value2 | part1); + value ^= 1068062556 ^ salt; + value = (value - _secretKey[199]) * 85627791; + return value; + } + case 46: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value = (value - _secretKey[200]) * 1138735395; + value ^= 196245895 ^ salt; + return value; + } + case 47: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 7; + uint part1 = (uint)value << (32 - 7); + value = (int)(value2 | part1); + value ^= -1964107221 ^ salt; + value = (value - _secretKey[10]) * -1413533649; + return value; + } + case 48: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 18; + uint part1 = (uint)value << (32 - 18); + value = (int)(value2 | part1); + value ^= -38668552 ^ salt; + value = (value - _secretKey[238]) * 665251331; + return value; + } + case 49: + { + // MultipleInstruction + value = (value - _secretKey[172] - salt) * -818173423; + return value; + } + case 50: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 11; + uint part2 = (uint)value << (32 - 11); + value = (int)(part1 | part2); + value ^= -2098495662 ^ salt; + value -= 1412414820 + _secretKey[219]; + return value; + } + case 51: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[97]); + uint part1 = value2 >> 10; + uint part2 = value2 << (32 - 10); + value = (int)(part1 | part2); + return value; + } + case 52: + { + // MultipleRotateXorInstruction + value ^= -21948406 ^ salt; + uint value2 = (uint)value >> 24; + uint part1 = (uint)value << (32 - 24); + value = (int)(value2 | part1); + value = (value - _secretKey[63]) * -99761833; + return value; + } + case 53: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[221]); + uint part1 = value2 >> 7; + uint part2 = value2 << (32 - 7); + value = (int)(part1 | part2); + return value; + } + case 54: + { + // MultipleInstruction + value = (value - _secretKey[96] - salt) * -2034360447; + return value; + } + case 55: + { + // MultipleRotateXorInstruction + value ^= -1022882984 ^ salt; + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value = (value - _secretKey[29]) * -899658607; + return value; + } + case 56: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 12; + uint part1 = (uint)value << (32 - 12); + value = (int)(value2 | part1); + value = (value - _secretKey[235]) * 340919493; + value ^= 2122077674 ^ salt; + return value; + } + case 57: + { + // AddRotateXorInstruction + value ^= -720824840 ^ salt; + uint value2 = (uint)value >> 21; + uint part1 = (uint)value << (32 - 21); + value = (int)(value2 | part1); + value -= -503448718 + _secretKey[175]; + return value; + } + case 58: + { + // MultipleInstruction + value = (value - _secretKey[231] - salt) * 913639057; + return value; + } + case 59: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[75]); + uint part1 = value2 >> 19; + uint part2 = value2 << (32 - 19); + value = (int)(part1 | part2); + return value; + } + case 60: + { + // MultipleRotateXorInstruction + value ^= 1420744071 ^ salt; + uint value2 = (uint)value >> 27; + uint part1 = (uint)value << (32 - 27); + value = (int)(value2 | part1); + value = (value - _secretKey[171]) * 238093953; + return value; + } + case 61: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 15; + uint part1 = (uint)value << (32 - 15); + value = (int)(value2 | part1); + value = (value - _secretKey[84]) * -2096065051; + value ^= 1366253139 ^ salt; + return value; + } + case 62: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 6; + uint part1 = (uint)value << (32 - 6); + value = (int)(value2 | part1); + value ^= -1974823163 ^ salt; + value = (value - _secretKey[132]) * 303900345; + return value; + } + case 63: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[119]); + uint part1 = value2 >> 22; + uint part2 = value2 << (32 - 22); + value = (int)(part1 | part2); + return value; + } + case 64: + { + // XorInstruction + value = ((value ^ 1262347216) - salt) ^ _secretKey[15]; + return value; + } + case 65: + { + // AddInstruction + value = ((value - 1780280992) ^ salt) - _secretKey[126]; + return value; + } + case 66: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 13; + uint part1 = (uint)value << (32 - 13); + value = (int)(value2 | part1); + value ^= -1198162446 ^ salt; + value = (value - _secretKey[85]) * 1314512283; + return value; + } + case 67: + { + // MultipleInstruction + value = (value - _secretKey[128] - salt) * 463417823; + return value; + } + case 68: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[178]); + uint part1 = value2 >> 24; + uint part2 = value2 << (32 - 24); + value = (int)(part1 | part2); + return value; + } + case 69: + { + // XorInstruction + value = ((value ^ 1649427052) - salt) ^ _secretKey[61]; + return value; + } + case 70: + { + // MultipleRotateXorInstruction + value ^= 849172121 ^ salt; + uint value2 = (uint)value >> 17; + uint part1 = (uint)value << (32 - 17); + value = (int)(value2 | part1); + value = (value - _secretKey[83]) * 368180765; + return value; + } + case 71: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[169]); + uint part1 = value2 >> 16; + uint part2 = value2 << (32 - 16); + value = (int)(part1 | part2); + return value; + } + case 72: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 20; + uint part1 = (uint)value << (32 - 20); + value = (int)(value2 | part1); + value -= -2092062916 + _secretKey[138]; + value ^= 1068287172 ^ salt; + return value; + } + case 73: + { + // MultipleInstruction + value = (value - _secretKey[34] - salt) * -495145181; + return value; + } + case 74: + { + // MultipleRotateXorInstruction + value ^= -145743337 ^ salt; + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value = (value - _secretKey[30]) * 1735064809; + return value; + } + case 75: + { + // MultipleRotateXorInstruction + value ^= 280941267 ^ salt; + uint value2 = (uint)value >> 4; + uint part1 = (uint)value << (32 - 4); + value = (int)(value2 | part1); + value = (value - _secretKey[142]) * 442035963; + return value; + } + case 76: + { + // MultipleInstruction + value = (value - _secretKey[156] - salt) * -971441783; + return value; + } + case 77: + { + // AddRotateXorInstruction + value ^= 827331935 ^ salt; + uint value2 = (uint)value >> 7; + uint part1 = (uint)value << (32 - 7); + value = (int)(value2 | part1); + value -= 314625916 + _secretKey[192]; + return value; + } + case 78: + { + // MultipleInstruction + value = (value - _secretKey[141] - salt) * -1649657957; + return value; + } + case 79: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 11; + uint part2 = (uint)value << (32 - 11); + value = (int)(part1 | part2); + value ^= -11549173 ^ salt; + value -= 1607953190 + _secretKey[133]; + return value; + } + case 80: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 25; + uint part1 = (uint)value << (32 - 25); + value = (int)(value2 | part1); + value -= 687186546 + _secretKey[95]; + value ^= -1454482890 ^ salt; + return value; + } + case 81: + { + // XorInstruction + value = ((value ^ 814860713) - salt) ^ _secretKey[195]; + return value; + } + case 82: + { + // AddRotateXorInstruction + value ^= 213310246 ^ salt; + uint value2 = (uint)value >> 19; + uint part1 = (uint)value << (32 - 19); + value = (int)(value2 | part1); + value -= 1998643542 + _secretKey[171]; + return value; + } + case 83: + { + // AddRotateXorInstruction + value ^= -696314173 ^ salt; + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value -= 542686146 + _secretKey[249]; + return value; + } + case 84: + { + // AddRotateXorInstruction + value ^= 118718247 ^ salt; + uint value2 = (uint)value >> 9; + uint part1 = (uint)value << (32 - 9); + value = (int)(value2 | part1); + value -= 1734820207 + _secretKey[2]; + return value; + } + case 85: + { + // XorInstruction + value = ((value ^ 1553710234) - salt) ^ _secretKey[143]; + return value; + } + case 86: + { + // AddRotateXorInstruction + value ^= -1402843691 ^ salt; + uint value2 = (uint)value >> 5; + uint part1 = (uint)value << (32 - 5); + value = (int)(value2 | part1); + value -= -217984331 + _secretKey[146]; + return value; + } + case 87: + { + // MultipleInstruction + value = (value - _secretKey[5] - salt) * -814971689; + return value; + } + case 88: + { + // MultipleRotateXorInstruction + value ^= -2044505542 ^ salt; + uint value2 = (uint)value >> 28; + uint part1 = (uint)value << (32 - 28); + value = (int)(value2 | part1); + value = (value - _secretKey[158]) * 681320217; + return value; + } + case 89: + { + // AddInstruction + value = ((value - -1213654475) ^ salt) - _secretKey[40]; + return value; + } + case 90: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 6; + uint part1 = (uint)value << (32 - 6); + value = (int)(value2 | part1); + value = (value - _secretKey[206]) * 583163349; + value ^= -1886972278 ^ salt; + return value; + } + case 91: + { + // AddRotateXorInstruction + value ^= -979249928 ^ salt; + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value -= -1724625239 + _secretKey[223]; + return value; + } + case 92: + { + // XorInstruction + value = ((value ^ -1104541704) - salt) ^ _secretKey[83]; + return value; + } + case 93: + { + // XorInstruction + value = ((value ^ 311150152) - salt) ^ _secretKey[31]; + return value; + } + case 94: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 17; + uint part1 = (uint)value << (32 - 17); + value = (int)(value2 | part1); + value = (value - _secretKey[176]) * 1535026385; + value ^= 116496631 ^ salt; + return value; + } + case 95: + { + // MultipleRotateXorInstruction + value ^= 2133438141 ^ salt; + uint value2 = (uint)value >> 2; + uint part1 = (uint)value << (32 - 2); + value = (int)(value2 | part1); + value = (value - _secretKey[87]) * 1779203413; + return value; + } + case 96: + { + // MultipleRotateXorInstruction + value ^= 225535005 ^ salt; + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value = (value - _secretKey[174]) * 521638757; + return value; + } + case 97: + { + // MultipleRotateXorInstruction + value ^= -1703839105 ^ salt; + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value = (value - _secretKey[175]) * -1131776573; + return value; + } + case 98: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 15; + uint part1 = (uint)value << (32 - 15); + value = (int)(value2 | part1); + value -= -1783079937 + _secretKey[78]; + value ^= -447564571 ^ salt; + return value; + } + case 99: + { + // XorInstruction + value = ((value ^ -316631669) - salt) ^ _secretKey[111]; + return value; + } + case 100: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 24; + uint part1 = (uint)value << (32 - 24); + value = (int)(value2 | part1); + value = (value - _secretKey[252]) * 898292471; + value ^= -405694625 ^ salt; + return value; + } + case 101: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 6; + uint part1 = (uint)value << (32 - 6); + value = (int)(value2 | part1); + value ^= -1551058348 ^ salt; + value = (value - _secretKey[212]) * -22167815; + return value; + } + case 102: + { + // AddInstruction + value = ((value - -853736135) ^ salt) - _secretKey[18]; + return value; + } + case 103: + { + // AddInstruction + value = ((value - 1321376878) ^ salt) - _secretKey[116]; + return value; + } + case 104: + { + // MultipleRotateXorInstruction + value ^= 541697309 ^ salt; + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value = (value - _secretKey[7]) * -1964889845; + return value; + } + case 105: + { + // AddRotateXorInstruction + value ^= 1176608900 ^ salt; + uint value2 = (uint)value >> 4; + uint part1 = (uint)value << (32 - 4); + value = (int)(value2 | part1); + value -= -822594180 + _secretKey[1]; + return value; + } + case 106: + { + // AddInstruction + value = ((value - -882893600) ^ salt) - _secretKey[110]; + return value; + } + case 107: + { + // MultipleRotateXorInstruction + value ^= -1566546809 ^ salt; + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value = (value - _secretKey[114]) * 406480373; + return value; + } + case 108: + { + // MultipleInstruction + value = (value - _secretKey[97] - salt) * 200608377; + return value; + } + case 109: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 7; + uint part1 = (uint)value << (32 - 7); + value = (int)(value2 | part1); + value -= 1161425930 + _secretKey[43]; + value ^= -72794161 ^ salt; + return value; + } + case 110: + { + // AddInstruction + value = ((value - -1619543125) ^ salt) - _secretKey[238]; + return value; + } + case 111: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 12; + uint part1 = (uint)value << (32 - 12); + value = (int)(value2 | part1); + value -= -1766468683 + _secretKey[241]; + value ^= 1170459122 ^ salt; + return value; + } + case 112: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 11; + uint part2 = (uint)value << (32 - 11); + value = (int)(part1 | part2); + value ^= 323019346 ^ salt; + value -= 251687012 + _secretKey[219]; + return value; + } + case 113: + { + // AddInstruction + value = ((value - -1049972438) ^ salt) - _secretKey[97]; + return value; + } + case 114: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 10; + uint part1 = (uint)value << (32 - 10); + value = (int)(value2 | part1); + value = (value - _secretKey[216]) * 594045631; + value ^= -492239002 ^ salt; + return value; + } + case 115: + { + // AddInstruction + value = ((value - -748126329) ^ salt) - _secretKey[221]; + return value; + } + case 116: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 17; + uint part2 = (uint)value << (32 - 17); + value = (int)(part1 | part2); + value ^= -5933889 ^ salt; + value -= -441051263 + _secretKey[96]; + return value; + } + case 117: + { + // MultipleRotateXorInstruction + value ^= 543895274 ^ salt; + uint value2 = (uint)value >> 11; + uint part1 = (uint)value << (32 - 11); + value = (int)(value2 | part1); + value = (value - _secretKey[88]) * 1253478165; + return value; + } + case 118: + { + // MultipleInstruction + value = (value - _secretKey[140] - salt) * 1897067971; + return value; + } + case 119: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[175]); + uint part1 = value2 >> 18; + uint part2 = value2 << (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 120: + { + // MultipleRotateXorInstruction + value ^= 1300544743 ^ salt; + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value = (value - _secretKey[57]) * -927555255; + return value; + } + case 121: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value -= 678597707 + _secretKey[119]; + value ^= -547380749 ^ salt; + return value; + } + case 122: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 19; + uint part1 = (uint)value << (32 - 19); + value = (int)(value2 | part1); + value = (value - _secretKey[191]) * -1394333385; + value ^= 683601851 ^ salt; + return value; + } + case 123: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[15]); + uint part1 = value2 >> 20; + uint part2 = value2 << (32 - 20); + value = (int)(part1 | part2); + return value; + } + case 124: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[132]); + uint part1 = value2 >> 8; + uint part2 = value2 << (32 - 8); + value = (int)(part1 | part2); + return value; + } + case 125: + { + // MultipleInstruction + value = (value - _secretKey[120] - salt) * 412809175; + return value; + } + case 126: + { + // AddInstruction + value = ((value - -1374139785) ^ salt) - _secretKey[197]; + return value; + } + case 127: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[26]); + uint part1 = value2 >> 15; + uint part2 = value2 << (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 128: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 21; + uint part1 = (uint)value << (32 - 21); + value = (int)(value2 | part1); + value -= 723646816 + _secretKey[146]; + value ^= 1415280510 ^ salt; + return value; + } + case 129: + { + // AddRotateXorInstruction + value ^= 2132310656 ^ salt; + uint value2 = (uint)value >> 30; + uint part1 = (uint)value << (32 - 30); + value = (int)(value2 | part1); + value -= 1410706317 + _secretKey[165]; + return value; + } + case 130: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 12; + uint part1 = (uint)value << (32 - 12); + value = (int)(value2 | part1); + value -= -483221582 + _secretKey[93]; + value ^= 1264971736 ^ salt; + return value; + } + case 131: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 17; + uint part2 = (uint)value << (32 - 17); + value = (int)(part1 | part2); + value ^= 1531807059 ^ salt; + value -= -1872731835 + _secretKey[53]; + return value; + } + case 132: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 10; + uint part2 = (uint)value << (32 - 10); + value = (int)(part1 | part2); + value ^= 306666665 ^ salt; + value -= 264114638 + _secretKey[240]; + return value; + } + case 133: + { + // AddRotateXorInstruction + value ^= -1266414649 ^ salt; + uint value2 = (uint)value >> 20; + uint part1 = (uint)value << (32 - 20); + value = (int)(value2 | part1); + value -= 824712252 + _secretKey[138]; + return value; + } + case 134: + { + // AddRotateXorInstruction + value ^= 2133737246 ^ salt; + uint value2 = (uint)value >> 25; + uint part1 = (uint)value << (32 - 25); + value = (int)(value2 | part1); + value -= -766337246 + _secretKey[35]; + return value; + } + case 135: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 14; + uint part2 = (uint)value << (32 - 14); + value = (int)(part1 | part2); + value ^= 1683555122 ^ salt; + value -= -1997614825 + _secretKey[165]; + return value; + } + case 136: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 28; + uint part1 = (uint)value << (32 - 28); + value = (int)(value2 | part1); + value ^= -1305042504 ^ salt; + value = (value - _secretKey[57]) * 949096539; + return value; + } + case 137: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[192]); + uint part1 = value2 >> 28; + uint part2 = value2 << (32 - 28); + value = (int)(part1 | part2); + return value; + } + case 138: + { + // XorInstruction + value = ((value ^ 1715223135) - salt) ^ _secretKey[135]; + return value; + } + case 139: + { + // MultipleInstruction + value = (value - _secretKey[79] - salt) * -307106491; + return value; + } + case 140: + { + // AddRotateXorInstruction + value ^= 806320034 ^ salt; + uint value2 = (uint)value >> 11; + uint part1 = (uint)value << (32 - 11); + value = (int)(value2 | part1); + value -= -408450171 + _secretKey[11]; + return value; + } + case 141: + { + // AddInstruction + value = ((value - -433941646) ^ salt) - _secretKey[95]; + return value; + } + case 142: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 26; + uint part2 = (uint)value << (32 - 26); + value = (int)(part1 | part2); + value ^= 2132471747 ^ salt; + value -= 1775820811 + _secretKey[169]; + return value; + } + case 143: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 2; + uint part1 = (uint)value << (32 - 2); + value = (int)(value2 | part1); + value ^= 114258470 ^ salt; + value = (value - _secretKey[19]) * 1566112771; + return value; + } + case 144: + { + // AddRotateXorInstruction + value ^= -1181275232 ^ salt; + uint value2 = (uint)value >> 3; + uint part1 = (uint)value << (32 - 3); + value = (int)(value2 | part1); + value -= -650723591 + _secretKey[206]; + return value; + } + case 145: + { + // MultipleInstruction + value = (value - _secretKey[169] - salt) * 39803307; + return value; + } + case 146: + { + // XorInstruction + value = ((value ^ -1297404981) - salt) ^ _secretKey[154]; + return value; + } + case 147: + { + // MultipleRotateXorInstruction + value ^= 801710213 ^ salt; + uint value2 = (uint)value >> 18; + uint part1 = (uint)value << (32 - 18); + value = (int)(value2 | part1); + value = (value - _secretKey[181]) * -1217833329; + return value; + } + case 148: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 29; + uint part2 = (uint)value << (32 - 29); + value = (int)(part1 | part2); + value ^= 1566976773 ^ salt; + value -= -1933121809 + _secretKey[230]; + return value; + } + case 149: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[188]); + uint part1 = value2 >> 30; + uint part2 = value2 << (32 - 30); + value = (int)(part1 | part2); + return value; + } + case 150: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[53]); + uint part1 = value2 >> 30; + uint part2 = value2 << (32 - 30); + value = (int)(part1 | part2); + return value; + } + case 151: + { + // AddInstruction + value = ((value - -2119615805) ^ salt) - _secretKey[138]; + return value; + } + case 152: + { + // MultipleRotateXorInstruction + value ^= 221292457 ^ salt; + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value = (value - _secretKey[102]) * -1630338257; + return value; + } + case 153: + { + // MultipleRotateXorInstruction + value ^= 1486712056 ^ salt; + uint value2 = (uint)value >> 13; + uint part1 = (uint)value << (32 - 13); + value = (int)(value2 | part1); + value = (value - _secretKey[248]) * -1360595481; + return value; + } + case 154: + { + // MultipleInstruction + value = (value - _secretKey[72] - salt) * 1812161233; + return value; + } + case 155: + { + // MultipleRotateXorInstruction + value ^= -81016400 ^ salt; + uint value2 = (uint)value >> 17; + uint part1 = (uint)value << (32 - 17); + value = (int)(value2 | part1); + value = (value - _secretKey[247]) * -47344461; + return value; + } + case 156: + { + // MultipleRotateXorInstruction + value ^= -1187848798 ^ salt; + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value = (value - _secretKey[252]) * -1858496529; + return value; + } + case 157: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 16; + uint part2 = (uint)value << (32 - 16); + value = (int)(part1 | part2); + value ^= 1185916334 ^ salt; + value -= 605454035 + _secretKey[108]; + return value; + } + case 158: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 1; + uint part2 = (uint)value << (32 - 1); + value = (int)(part1 | part2); + value ^= -451761745 ^ salt; + value -= 2112611413 + _secretKey[235]; + return value; + } + case 159: + { + // XorInstruction + value = ((value ^ 1660696922) - salt) ^ _secretKey[229]; + return value; + } + case 160: + { + // MultipleRotateXorInstruction + value ^= -431219573 ^ salt; + uint value2 = (uint)value >> 31; + uint part1 = (uint)value << (32 - 31); + value = (int)(value2 | part1); + value = (value - _secretKey[111]) * 1706905775; + return value; + } + case 161: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 28; + uint part1 = (uint)value << (32 - 28); + value = (int)(value2 | part1); + value = (value - _secretKey[198]) * -580820577; + value ^= 1658933717 ^ salt; + return value; + } + case 162: + { + // AddRotateXorInstruction + value ^= 609336148 ^ salt; + uint value2 = (uint)value >> 20; + uint part1 = (uint)value << (32 - 20); + value = (int)(value2 | part1); + value -= -1278798944 + _secretKey[72]; + return value; + } + case 163: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 26; + uint part1 = (uint)value << (32 - 26); + value = (int)(value2 | part1); + value ^= -1055021038 ^ salt; + value = (value - _secretKey[57]) * -620746171; + return value; + } + case 164: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 7; + uint part1 = (uint)value << (32 - 7); + value = (int)(value2 | part1); + value ^= 1389308323 ^ salt; + value = (value - _secretKey[225]) * 2035776477; + return value; + } + case 165: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value -= -1939584600 + _secretKey[124]; + value ^= 318043677 ^ salt; + return value; + } + case 166: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value ^= 2047590880 ^ salt; + value = (value - _secretKey[184]) * -748679859; + return value; + } + case 167: + { + // XorInstruction + value = ((value ^ -312123044) - salt) ^ _secretKey[114]; + return value; + } + case 168: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 1; + uint part2 = (uint)value << (32 - 1); + value = (int)(part1 | part2); + value ^= -1299860280 ^ salt; + value -= 1897551751 + _secretKey[139]; + return value; + } + case 169: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[10]); + uint part1 = value2 >> 15; + uint part2 = value2 << (32 - 15); + value = (int)(part1 | part2); + return value; + } + case 170: + { + // MultipleInstruction + value = (value - _secretKey[60] - salt) * 609589815; + return value; + } + case 171: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 21; + uint part1 = (uint)value << (32 - 21); + value = (int)(value2 | part1); + value = (value - _secretKey[242]) * 893240649; + value ^= 820953326 ^ salt; + return value; + } + case 172: + { + // XorInstruction + value = ((value ^ 921116076) - salt) ^ _secretKey[89]; + return value; + } + case 173: + { + // AddInstruction + value = ((value - -1261901861) ^ salt) - _secretKey[82]; + return value; + } + case 174: + { + // MultipleRotateXorInstruction + value ^= -1758870671 ^ salt; + uint value2 = (uint)value >> 1; + uint part1 = (uint)value << (32 - 1); + value = (int)(value2 | part1); + value = (value - _secretKey[42]) * -1163763527; + return value; + } + case 175: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value -= 1254595032 + _secretKey[10]; + value ^= -99371457 ^ salt; + return value; + } + case 176: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 0; + uint part2 = (uint)value << (32 - 0); + value = (int)(part1 | part2); + value ^= -2036462975 ^ salt; + value -= 1901168605 + _secretKey[131]; + return value; + } + case 177: + { + // MultipleRotateXorInstruction + value ^= -805817000 ^ salt; + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value = (value - _secretKey[29]) * -1809334639; + return value; + } + case 178: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 12; + uint part2 = (uint)value << (32 - 12); + value = (int)(part1 | part2); + value ^= 762578411 ^ salt; + value -= 28490730 + _secretKey[13]; + return value; + } + case 179: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 24; + uint part1 = (uint)value << (32 - 24); + value = (int)(value2 | part1); + value -= -41308497 + _secretKey[85]; + value ^= 1812019570 ^ salt; + return value; + } + case 180: + { + // XorInstruction + value = ((value ^ 1563652208) - salt) ^ _secretKey[231]; + return value; + } + case 181: + { + // AddRotateXorInstruction + value ^= -1148350591 ^ salt; + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value -= -542613261 + _secretKey[75]; + return value; + } + case 182: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 19; + uint part2 = (uint)value << (32 - 19); + value = (int)(part1 | part2); + value ^= 1842755263 ^ salt; + value -= 1462569147 + _secretKey[135]; + return value; + } + case 183: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 8; + uint part1 = (uint)value << (32 - 8); + value = (int)(value2 | part1); + value ^= -159244912 ^ salt; + value = (value - _secretKey[15]) * -663204867; + return value; + } + case 184: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[230]); + uint part1 = value2 >> 5; + uint part2 = value2 << (32 - 5); + value = (int)(part1 | part2); + return value; + } + case 185: + { + // AddRotateXorInstruction + value ^= 1218428368 ^ salt; + uint value2 = (uint)value >> 5; + uint part1 = (uint)value << (32 - 5); + value = (int)(value2 | part1); + value -= 45305078 + _secretKey[119]; + return value; + } + case 186: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 0; + uint part2 = (uint)value << (32 - 0); + value = (int)(part1 | part2); + value ^= 567100030 ^ salt; + value -= 148564506 + _secretKey[160]; + return value; + } + case 187: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[242]); + uint part1 = value2 >> 21; + uint part2 = value2 << (32 - 21); + value = (int)(part1 | part2); + return value; + } + case 188: + { + // MultipleRotateXorInstruction + value ^= 730302816 ^ salt; + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value = (value - _secretKey[30]) * -1488719571; + return value; + } + case 189: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[93]); + uint part1 = value2 >> 18; + uint part2 = value2 << (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 190: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[69]); + uint part1 = value2 >> 29; + uint part2 = value2 << (32 - 29); + value = (int)(part1 | part2); + return value; + } + case 191: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value = (value - _secretKey[153]) * 1590990225; + value ^= 1474104403 ^ salt; + return value; + } + case 192: + { + // AddInstruction + value = ((value - 1345231273) ^ salt) - _secretKey[170]; + return value; + } + case 193: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[138]); + uint part1 = value2 >> 28; + uint part2 = value2 << (32 - 28); + value = (int)(part1 | part2); + return value; + } + case 194: + { + // AddRotateXorInstruction + value ^= -1086752221 ^ salt; + uint value2 = (uint)value >> 2; + uint part1 = (uint)value << (32 - 2); + value = (int)(value2 | part1); + value -= -1649692985 + _secretKey[138]; + return value; + } + case 195: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 5; + uint part1 = (uint)value << (32 - 5); + value = (int)(value2 | part1); + value = (value - _secretKey[23]) * -718602987; + value ^= 216360478 ^ salt; + return value; + } + case 196: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 25; + uint part1 = (uint)value << (32 - 25); + value = (int)(value2 | part1); + value -= -922639548 + _secretKey[211]; + value ^= 1736125070 ^ salt; + return value; + } + case 197: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value -= -1729078426 + _secretKey[124]; + value ^= -1590872932 ^ salt; + return value; + } + case 198: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 13; + uint part2 = (uint)value << (32 - 13); + value = (int)(part1 | part2); + value ^= 2101329043 ^ salt; + value -= -2086916257 + _secretKey[135]; + return value; + } + case 199: + { + // XorInstruction + value = ((value ^ -1188487898) - salt) ^ _secretKey[133]; + return value; + } + case 200: + { + // MultipleInstruction + value = (value - _secretKey[162] - salt) * -1592735389; + return value; + } + case 201: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 11; + uint part1 = (uint)value << (32 - 11); + value = (int)(value2 | part1); + value -= -1882888353 + _secretKey[153]; + value ^= 2087683186 ^ salt; + return value; + } + case 202: + { + // MultipleRotateXorInstruction + value ^= -1855365205 ^ salt; + uint value2 = (uint)value >> 22; + uint part1 = (uint)value << (32 - 22); + value = (int)(value2 | part1); + value = (value - _secretKey[58]) * -1429384213; + return value; + } + case 203: + { + // MultipleInstruction + value = (value - _secretKey[66] - salt) * 180652695; + return value; + } + case 204: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 0; + uint part1 = (uint)value << (32 - 0); + value = (int)(value2 | part1); + value ^= 1668989123 ^ salt; + value = (value - _secretKey[206]) * 715760713; + return value; + } + case 205: + { + // MultipleInstruction + value = (value - _secretKey[169] - salt) * -19415893; + return value; + } + case 206: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value = (value - _secretKey[143]) * 408562579; + value ^= -387621173 ^ salt; + return value; + } + case 207: + { + // XorInstruction + value = ((value ^ -1302837102) - salt) ^ _secretKey[133]; + return value; + } + case 208: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 29; + uint part1 = (uint)value << (32 - 29); + value = (int)(value2 | part1); + value = (value - _secretKey[5]) * 199587543; + value ^= 1201861103 ^ salt; + return value; + } + case 209: + { + // AddInstruction + value = ((value - -1698116194) ^ salt) - _secretKey[188]; + return value; + } + case 210: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 3; + uint part1 = (uint)value << (32 - 3); + value = (int)(value2 | part1); + value -= -5412811 + _secretKey[40]; + value ^= 955827838 ^ salt; + return value; + } + case 211: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value ^= -261186202 ^ salt; + value = (value - _secretKey[206]) * 250403797; + return value; + } + case 212: + { + // XorInstruction + value = ((value ^ 1451245279) - salt) ^ _secretKey[215]; + return value; + } + case 213: + { + // AddInstruction + value = ((value - -48271475) ^ salt) - _secretKey[248]; + return value; + } + case 214: + { + // MultipleRotateXorInstruction + value ^= 280704379 ^ salt; + uint value2 = (uint)value >> 31; + uint part1 = (uint)value << (32 - 31); + value = (int)(value2 | part1); + value = (value - _secretKey[72]) * -1743417391; + return value; + } + case 215: + { + // MultipleRotateXorInstruction + value ^= -1507466225 ^ salt; + uint value2 = (uint)value >> 17; + uint part1 = (uint)value << (32 - 17); + value = (int)(value2 | part1); + value = (value - _secretKey[176]) * -795921711; + return value; + } + case 216: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[162]); + uint part1 = value2 >> 23; + uint part2 = value2 << (32 - 23); + value = (int)(part1 | part2); + return value; + } + case 217: + { + // XorInstruction + value = ((value ^ -1329546797) - salt) ^ _secretKey[108]; + return value; + } + case 218: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 11; + uint part1 = (uint)value << (32 - 11); + value = (int)(value2 | part1); + value -= 1710889501 + _secretKey[85]; + value ^= 846489904 ^ salt; + return value; + } + case 219: + { + // XorInstruction + value = ((value ^ -339712479) - salt) ^ _secretKey[127]; + return value; + } + case 220: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 15; + uint part1 = (uint)value << (32 - 15); + value = (int)(value2 | part1); + value -= -308188673 + _secretKey[78]; + value ^= -1008587035 ^ salt; + return value; + } + case 221: + { + // MultipleInstruction + value = (value - _secretKey[111] - salt) * 773277731; + return value; + } + case 222: + { + // MultipleInstruction + value = (value - _secretKey[198] - salt) * 1149219487; + return value; + } + case 223: + { + // AddInstruction + value = ((value - -449129672) ^ salt) - _secretKey[160]; + return value; + } + case 224: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 12; + uint part1 = (uint)value << (32 - 12); + value = (int)(value2 | part1); + value ^= -180027834 ^ salt; + value = (value - _secretKey[84]) * 102372989; + return value; + } + case 225: + { + // XorInstruction + value = ((value ^ 946019090) - salt) ^ _secretKey[58]; + return value; + } + case 226: + { + // AddRotateXorInstruction + value ^= 1350981383 ^ salt; + uint value2 = (uint)value >> 3; + uint part1 = (uint)value << (32 - 3); + value = (int)(value2 | part1); + value -= 694016884 + _secretKey[225]; + return value; + } + case 227: + { + // AddRotateXorInstruction + value ^= 1680252929 ^ salt; + uint value2 = (uint)value >> 28; + uint part1 = (uint)value << (32 - 28); + value = (int)(value2 | part1); + value -= -870643939 + _secretKey[168]; + return value; + } + case 228: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[184]); + uint part1 = value2 >> 4; + uint part2 = value2 << (32 - 4); + value = (int)(part1 | part2); + return value; + } + case 229: + { + // AddRotateXorInstruction + value ^= -1727043214 ^ salt; + uint value2 = (uint)value >> 28; + uint part1 = (uint)value << (32 - 28); + value = (int)(value2 | part1); + value -= 1785715822 + _secretKey[199]; + return value; + } + case 230: + { + // XorInstruction + value = ((value ^ -1277148537) - salt) ^ _secretKey[139]; + return value; + } + case 231: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 10; + uint part1 = (uint)value << (32 - 10); + value = (int)(value2 | part1); + value ^= 1940873679 ^ salt; + value = (value - _secretKey[226]) * -528829791; + return value; + } + case 232: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 14; + uint part1 = (uint)value << (32 - 14); + value = (int)(value2 | part1); + value = (value - _secretKey[171]) * 1486956053; + value ^= -879839609 ^ salt; + return value; + } + case 233: + { + // AddInstruction + value = ((value - 328489970) ^ salt) - _secretKey[181]; + return value; + } + case 234: + { + // MultipleInstruction + value = (value - _secretKey[89] - salt) * -1387476699; + return value; + } + case 235: + { + // XorAddRotateInstruction + uint value2 = (uint)value >> 8; + uint part1 = (uint)value << (32 - 8); + value = (int)(value2 | part1); + value -= -1977097134 + _secretKey[203]; + value ^= 1290000091 ^ salt; + return value; + } + case 236: + { + // AddInstruction + value = ((value - 1890859361) ^ salt) - _secretKey[113]; + return value; + } + case 237: + { + // AddRotateXorInstruction + value ^= 1434413518 ^ salt; + uint value2 = (uint)value >> 10; + uint part1 = (uint)value << (32 - 10); + value = (int)(value2 | part1); + value -= 1045620543 + _secretKey[216]; + return value; + } + case 238: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 0; + uint part2 = (uint)value << (32 - 0); + value = (int)(part1 | part2); + value ^= 1591345537 ^ salt; + value -= -1706485027 + _secretKey[131]; + return value; + } + case 239: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 24; + uint part2 = (uint)value << (32 - 24); + value = (int)(part1 | part2); + value ^= 1117669949 ^ salt; + value -= 1271081841 + _secretKey[29]; + return value; + } + case 240: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 12; + uint part1 = (uint)value << (32 - 12); + value = (int)(value2 | part1); + value = (value - _secretKey[235]) * 1588287429; + value ^= -842525462 ^ salt; + return value; + } + case 241: + { + // AddInstruction + value = ((value - 1030822002) ^ salt) - _secretKey[175]; + return value; + } + case 242: + { + // MultipleRotateXorInstruction + value ^= 811922151 ^ salt; + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value = (value - _secretKey[57]) * 769862473; + return value; + } + case 243: + { + // AddRotateXorInstruction + value ^= -1258702719 ^ salt; + uint value2 = (uint)value >> 23; + uint part1 = (uint)value << (32 - 23); + value = (int)(value2 | part1); + value -= 656680947 + _secretKey[75]; + return value; + } + case 244: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 19; + uint part1 = (uint)value << (32 - 19); + value = (int)(value2 | part1); + value = (value - _secretKey[191]) * -883345609; + value ^= 90809787 ^ salt; + return value; + } + case 245: + { + // AddRotateXorInstruction + value ^= 99436168 ^ salt; + uint value2 = (uint)value >> 16; + uint part1 = (uint)value << (32 - 16); + value = (int)(value2 | part1); + value -= -532913580 + _secretKey[15]; + return value; + } + case 246: + { + // MultipleXorRotateInstruction + uint value2 = (uint)value >> 22; + uint part1 = (uint)value << (32 - 22); + value = (int)(value2 | part1); + value ^= 1471598712 ^ salt; + value = (value - _secretKey[230]) * -599880499; + return value; + } + case 247: + { + // XorMultipleRotateInstruction + uint value2 = (uint)value >> 26; + uint part1 = (uint)value << (32 - 26); + value = (int)(value2 | part1); + value = (value - _secretKey[15]) * -1447936463; + value ^= 1941306053 ^ salt; + return value; + } + case 248: + { + // AddRotateXorInstruction + value ^= 2045091157 ^ salt; + uint value2 = (uint)value >> 18; + uint part1 = (uint)value << (32 - 18); + value = (int)(value2 | part1); + value -= 883137918 + _secretKey[96]; + return value; + } + case 249: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[165]); + uint part1 = value2 >> 13; + uint part2 = value2 << (32 - 13); + value = (int)(part1 | part2); + return value; + } + case 250: + { + // AddInstruction + value = ((value - -394947456) ^ salt) - _secretKey[96]; + return value; + } + case 251: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[93]); + uint part1 = value2 >> 18; + uint part2 = value2 << (32 - 18); + value = (int)(part1 | part2); + return value; + } + case 252: + { + // AddInstruction + value = ((value - 1917332797) ^ salt) - _secretKey[69]; + return value; + } + case 253: + { + // AddXorRotateInstruction + uint part1 = (uint)value >> 14; + uint part2 = (uint)value << (32 - 14); + value = (int)(part1 | part2); + value ^= -1509317223 ^ salt; + value -= 1006809939 + _secretKey[113]; + return value; + } + case 254: + { + // BitRotateInstruction + uint value2 = (uint)((value - salt) ^ _secretKey[170]); + uint part1 = value2 >> 9; + uint part2 = value2 << (32 - 9); + value = (int)(part1 | part2); + return value; + } + case 255: + { + // AddInstruction + value = ((value - 683715132) ^ salt) - _secretKey[138]; + return value; + } + + default: + throw new System.Exception($"Invalid opCode:{opCode}"); + } + } + + } +} + diff --git a/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs.meta b/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs.meta new file mode 100644 index 00000000..639feea7 --- /dev/null +++ b/UnityProject/Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 673c197d012a9c549be6c6991ad3fb30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Obfuz/ObfuzInitialize.cs b/UnityProject/Assets/Obfuz/ObfuzInitialize.cs new file mode 100644 index 00000000..c038d7e4 --- /dev/null +++ b/UnityProject/Assets/Obfuz/ObfuzInitialize.cs @@ -0,0 +1,24 @@ +using Obfuz; +using Obfuz.EncryptionVM; +using UnityEngine; + +namespace Launcher +{ + public class ObfuzInitialize : MonoBehaviour + { + // 初始化EncryptionService后被混淆的代码才能正常运行, + // 因此尽可能地早地初始化它。 + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + private static void SetUpStaticSecretKey() + { +#if ENABLE_OBFUZ + Debug.Log("Enable Obfuz"); + Debug.Log("SetUpStaticSecret begin"); + EncryptionService.Encryptor = new GeneratedEncryptionVirtualMachine(Resources.Load("Obfuz/defaultStaticSecretKey").bytes); + Debug.Log("SetUpStaticSecret end"); +#else + Debug.Log("Disable Obfuz"); +#endif + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Obfuz/ObfuzInitialize.cs.meta b/UnityProject/Assets/Obfuz/ObfuzInitialize.cs.meta new file mode 100644 index 00000000..759665cf --- /dev/null +++ b/UnityProject/Assets/Obfuz/ObfuzInitialize.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 883bcac172734133a1972a05275df53b +timeCreated: 1753458333 \ No newline at end of file diff --git a/UnityProject/Assets/Obfuz/SymbolObfus.meta b/UnityProject/Assets/Obfuz/SymbolObfus.meta new file mode 100644 index 00000000..0657f9d3 --- /dev/null +++ b/UnityProject/Assets/Obfuz/SymbolObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 25a2e7cc325e1054db9d6fd1189d5474 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml b/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml new file mode 100644 index 00000000..510f5e90 --- /dev/null +++ b/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml @@ -0,0 +1,9389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml.meta b/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml.meta new file mode 100644 index 00000000..4589172d --- /dev/null +++ b/UnityProject/Assets/Obfuz/SymbolObfus/symbol-mapping.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b16602403e80c24f85f2d87b7db8bcf +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Resources.meta b/UnityProject/Assets/Resources.meta new file mode 100644 index 00000000..71dbebea --- /dev/null +++ b/UnityProject/Assets/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca6f6fb6ba786e14fb3096bd01f61ac5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Resources/Obfuz.meta b/UnityProject/Assets/Resources/Obfuz.meta new file mode 100644 index 00000000..896ee908 --- /dev/null +++ b/UnityProject/Assets/Resources/Obfuz.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 029862980bde3c9489ef31bf924728e3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes b/UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes new file mode 100644 index 0000000000000000000000000000000000000000..666203486e21874a44414fa9318f227e3e0ed94e GIT binary patch literal 1024 zcmV+b1poWg+~lD27bqc4$KVEi4UVl-IV#nod0ZH**l1jmp_mkF|B#~9*7#TsUhK+k z71&|ydybMXNJJIyaAVin@QkY092#ip$W|ZeD|32vQ?@%83;{RAv8=W^?9+qVjx+7D z@gF(q%Um*%3T#%O)-JQ)>9g3K^LlgP<)$ZWUT(5Dtsw$TuM(a^xyBfy&5DnXtJ8SPWXW`>b+x54(~# z)N?QWsn>Tg(BRzL;4DVvn}@-_U(0?OM;XMuC1^J zaB597P88Iu*wRT3^L%yL1yfoo?^eINa#BV_laBBl4(8HQ661INoxXt*^NW#ZwXmh! z<5k4Ep8V2FtrQQTIC&VOH~Hd_XXa_YmRMFb8iw-N1Ez${2x~a zDI5=F0_gsmj<0$OX$p4pyba6EyRo>+yGN_&dIc_J&b!KARJ%j#f=R^2QyIvvRb!mY zDJI$zNYH`wXCbd#I$)y$$TL2Ulm!sMj+K)rGEztZWI8l(Dl50aaCtzu(w6@?Rfzy+ zf_AzVgk2$YS=vutD?4jbrX!(q772{hpteh{)8F>sM9L{+{ws(#r@xF$bd&#G3WdsB zo&fTuRXT@*_%uCjyYORv6QZC@Ji@&9cnzURzd2M5vm`M*51&av+{UdiJoepZGM`6L zb3rHn;6GVwMS=qqV;IeX!ZskC$j1hp;WzLPL;72?wOnJF7TeNxS=q~ zFf~ACZr3T*O6fL%xxF-%#J9gL!!G9TO$Qjh=A1GoSy6m##K;SjsPeslp}*J9(kQA{ zb^gIsnRqE3k4DVG`xiTJh3xE!uJw2r6P#?72~yUv+QS~^9s#TQr43E06)X(D{{b=} zO`%M>C}Ij2<)qVb1a@=~EQnOb?#MoKx@b=U=(X7u#89vfDpgHga?m-gLFi5RNY5oG zyqt*FQgRu(MrkdvrZTH#kyy_0Atu|D7Z+$16FR7`*nYX}M{ly-Xt@wO0nIO&ga?iw ujO^{`zvJR~ZG{!VZ33ZtQY20PjZ)Ic$;ewkQYr`Yq*rfuc4{rsVd#X_rTQBH literal 0 HcmV?d00001 diff --git a/UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes.meta b/UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes.meta new file mode 100644 index 00000000..d903a4d7 --- /dev/null +++ b/UnityProject/Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0ed52d75209dd9e4c81e0cab914d053c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Resources/Obfuz/defaultStaticSecretKey.bytes b/UnityProject/Assets/Resources/Obfuz/defaultStaticSecretKey.bytes new file mode 100644 index 0000000000000000000000000000000000000000..d91a82dafebb3f968af0759569b8a7b8b162caa2 GIT binary patch literal 1024 zcmV+b1poWuad=sgU47iFfE^dzNj-D}T&^6H@!3j69H%>yX!OuzZE>_-*Vn^UeVP~V znCw*zBLM6c-rl0iwZK-SOug5kv6fMroS^SoI3`j|5 z!SuGkggBksOZddck3SQXzp(i=D4!I#7r!RZ!+th_7!Txw;uE@H<=}jFB5XhJR5Z2m zz%x4np0VZ=SM~eT{6G9tV!zy)Iv0muv+vygbvI^QPf>8JdZT9imNrBK`}m_)JS$ej zmRD6KT~WC)bhJyO?@p#>D6ctTtp>f%5hx!9FGwGBr|M2qA#Uh8IRR3|Z!x>)abprW z9YHcNjICn{6mm(0dl}QQxmi?I2Zv%duwY*GkXTCzFQp@^H?`xe1c3!@?7BiY<-)RH z&Em2Gam4#bq=AC+L4zC*RzKUOG?HSS30m$cWAy6@OjbMQw(kDuC6^TTm|Bw znvkQl^CP4dw{iE>Tz@Q7N}rUJl?c@8X*H*idqHxBn{w5E#TB>CQF^fWyC)bA{!c+L z=K?neYbQzVb|t+%!S7yJD&=XysmLU;R~Xk)pGrs`rpsm~d*i9eL& zQD%&XlNpG|i7NxS`TV_r{^8K7x4?otR00pii2!Vde1(bi*{p4RoIEQRw-f+X3P_#( zrp!WG7ZBu9c!m1{XqO&+FP+7Y%8MYCC#IxsOs=$CusY55M;!>J$IJ&6KJp0Wv>8CM z>Zmav;(f&5xU(s8^&vz?i55tIo_#P;2;7SKze@gZ+6lpzHrihG6sD?()5Hrx?&inf zHot&$`tby?&W5Gz7*(g=z~%UF5_8DG8b+=V^~|&PC|8={-o7g&VTVDCVf|!2%IJRl zvQxF}W3D$G2C?hHlk!yzz;%&z+os2@p$kF?ki6cAcVW3UIcecP%fVzYAm*O*r-hQY~9md%2 zN*Yc~i;-p*j?JOV63t>ALm?;{$q=8!)NxKCXKy{7{+X4jc_j4(i!k^8y=>V7YaG(C z(N_x|t~iZLl3ctS06b`NK5tz!MQ~3Bgqeu$v?dx*9a<(t4iqxq98aKtP4<|tFT@4^ /// 禁用HybridCLR宏定义。 /// [MenuItem("HybridCLR/Define Symbols/Disable HybridCLR", false, 30)] - public static void Disable() + public static void DisableHybridCLR() { ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableHybridClrScriptingDefineSymbol); #if ENABLE_HYBRIDCLR @@ -27,7 +33,7 @@ public static class BuildDLLCommand /// 开启HybridCLR宏定义。 /// [MenuItem("HybridCLR/Define Symbols/Enable HybridCLR", false, 31)] - public static void Enable() + public static void EnableHybridCLR() { ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableHybridClrScriptingDefineSymbol); ScriptingDefineSymbols.AddScriptingDefineSymbol(EnableHybridClrScriptingDefineSymbol); @@ -36,6 +42,31 @@ public static class BuildDLLCommand UpdateSettingEditor.ForceUpdateAssemblies(); #endif } + #endregion + + #region Obfuz/Define Symbols + /// + /// 禁用Obfuz宏定义。 + /// + [MenuItem("HybridCLR/Define Symbols/Disable Obfuz", false, 30)] + public static void DisableObfuz() + { + ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableObfuzScriptingDefineSymbol); + ObfuzSettings.Instance.buildPipelineSettings.enable = false; + } + + /// + /// 开启Obfuz宏定义。 + /// + [MenuItem("HybridCLR/Define Symbols/Enable Obfuz", false, 31)] + public static void EnableObfuz() + { + ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableObfuzScriptingDefineSymbol); + ScriptingDefineSymbols.AddScriptingDefineSymbol(EnableObfuzScriptingDefineSymbol); + ObfuzSettings.Instance.buildPipelineSettings.enable = true; + } + #endregion + [MenuItem("HybridCLR/Build/BuildAssets And CopyTo AssemblyTextAssetPath")] public static void BuildAndCopyDlls() @@ -59,6 +90,31 @@ public static class BuildDLLCommand { CopyAOTAssembliesToAssetPath(); CopyHotUpdateAssembliesToAssetPath(); + +#if ENABLE_HYBRIDCLR && ENABLE_OBFUZ + CompileDllCommand.CompileDll(target); + + string obfuscatedHotUpdateDllPath = PrebuildCommandExt.GetObfuscatedHotUpdateAssemblyOutputPath(target); + ObfuscateUtil.ObfuscateHotUpdateAssemblies(target, obfuscatedHotUpdateDllPath); + + Directory.CreateDirectory(Application.streamingAssetsPath); + + string hotUpdateDllPath = $"{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target)}"; + List obfuscationRelativeAssemblyNames = ObfuzSettings.Instance.assemblySettings.GetObfuscationRelativeAssemblyNames(); + + foreach (string assName in SettingsUtil.HotUpdateAssemblyNamesIncludePreserved) + { + string srcDir = obfuscationRelativeAssemblyNames.Contains(assName) ? obfuscatedHotUpdateDllPath : hotUpdateDllPath; + string srcFile = $"{srcDir}/{assName}.dll"; + string dstFile = Application.dataPath +"/"+ TEngine.Settings.UpdateSetting.AssemblyTextAssetPath + $"/{assName}.dll.bytes"; + if (File.Exists(srcFile)) + { + File.Copy(srcFile, dstFile, true); + Debug.Log($"[CompileAndObfuscate] Copy {srcFile} to {dstFile}"); + } + } +#endif + AssetDatabase.Refresh(); } diff --git a/UnityProject/Assets/TEngine/Editor/TEngine.Editor.asmdef b/UnityProject/Assets/TEngine/Editor/TEngine.Editor.asmdef index 901019b7..aa8a9db5 100644 --- a/UnityProject/Assets/TEngine/Editor/TEngine.Editor.asmdef +++ b/UnityProject/Assets/TEngine/Editor/TEngine.Editor.asmdef @@ -7,7 +7,9 @@ "GUID:4d1926c9df5b052469a1c63448b7609a", "GUID:6e76b07590314a543b982daed6af2509", "GUID:2373f786d14518f44b0f475db77ba4de", - "GUID:6055be8ebefd69e48b49212b09b47b2f" + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:3743e71edcd5bd8499007797ef02cbfb", + "GUID:66e09fc524ec6594b8d6ca1d91aa1a41" ], "includePlatforms": [ "Editor" diff --git a/UnityProject/Assets/TEngine/Settings/Prefab/GameEntry.prefab b/UnityProject/Assets/TEngine/Settings/Prefab/GameEntry.prefab index a9071164..fb6ba334 100644 --- a/UnityProject/Assets/TEngine/Settings/Prefab/GameEntry.prefab +++ b/UnityProject/Assets/TEngine/Settings/Prefab/GameEntry.prefab @@ -47,6 +47,50 @@ MonoBehaviour: innerLocalizationCsv: {fileID: 0} allLanguage: [] useRuntimeModule: 1 +--- !u!1 &7471325143993775112 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5905096077013678464} + - component: {fileID: 7968529201080193016} + m_Layer: 0 + m_Name: Obfuz + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5905096077013678464 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7471325143993775112} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7696541955266276700} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &7968529201080193016 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7471325143993775112} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 883bcac172734133a1972a05275df53b, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &7696541953913300370 GameObject: m_ObjectHideFlags: 0 @@ -208,6 +252,7 @@ MonoBehaviour: useSystemUnloadUnusedAssets: 1 packageName: DefaultPackage playMode: 0 + encryptionType: 0 updatableWhilePlaying: 0 milliseconds: 30 downloadingMaxNum: 10 @@ -266,6 +311,7 @@ Transform: - {fileID: 7696541955235861479} - {fileID: 404609667950854598} - {fileID: 5550035055454355162} + - {fileID: 5905096077013678464} m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Mac.dll b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Mac.dll new file mode 100644 index 0000000000000000000000000000000000000000..2cce7a755ffa4c28e6b7f5f1d24d01ed06a8b5ca GIT binary patch literal 955904 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�LEsEql&uQIyFW@s2#s6Au`T zOv>oxO(br-a%whr5-F9VPJYYfmUqf&_JRLSTF!@o1SR>hww|`trDa-SDX(`5E-n!KGj7-PcVYQj?;#eV!@F>oa(c0} zF~Rb(T=1?nB%=()=AvBi@%Upf7koATm{;EhkynK>Bl^mC1IFQP8@|zBCSXE(rQ^-w zBPB}p>#PKHeSJUWKaBEEZ7&Ly#>^<`!;UK<+Fr&`hO4bpnwtK@?dcaM=_SOpx8l^a zE=P!i|Dk3%W#EG!;!K;%*pK8!&Co(H)EK)$nrp}o3cDp5WcGv(#03?4E`r z!_1okXS=D9X2@)6lv`79Fu5V+3Hv1US%0A@_A#l>{I;H;Lf&AQa`_|l1iDcV$ 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�Yt~bx>DPvt-U3p3LZEGCC+0PNyJd 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%0A>WdQC6aJvE@ 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 literal 0 HcmV?d00001 diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Win.dll b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/ModifiedUnityAssemblies/2019.4.40/Unity.IL2CPP-Win.dll new file mode 100644 index 0000000000000000000000000000000000000000..17135a13eb77ada22a786a9108a9cdfb80a2f7aa GIT binary patch literal 954368 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 I{|ABp0U}d|>;M1& literal 0 HcmV?d00001 diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll new file mode 100644 index 0000000000000000000000000000000000000000..250cf817385cd5d2ea5cb8e72f00c5f5cab50ee6 GIT binary patch literal 84992 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 D+Hv%0 literal 0 HcmV?d00001 diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/AssemblyManifest.cpp.tpl b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/AssemblyManifest.cpp.tpl new file mode 100644 index 00000000..d43977db --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/AssemblyManifest.cpp.tpl @@ -0,0 +1,12 @@ +#include "../Il2CppCompatibleDef.h" + +namespace hybridclr +{ + const char* g_placeHolderAssemblies[] = + { + //!!!{{PLACE_HOLDER + + //!!!}}PLACE_HOLDER + nullptr, + }; +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/MethodBridge.cpp.tpl b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/MethodBridge.cpp.tpl new file mode 100644 index 00000000..b723a161 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/MethodBridge.cpp.tpl @@ -0,0 +1,32 @@ +#include +#if HYBRIDCLR_UNITY_2023_OR_NEW +#include +#elif HYBRIDCLR_UNITY_2022 +#include +#elif HYBRIDCLR_UNITY_2020 || HYBRIDCLR_UNITY_2021 +#include +#else +#include +#endif + +#include "vm/ClassInlines.h" +#include "vm/Object.h" +#include "vm/Class.h" +#include "vm/ScopedThreadAttacher.h" + +#include "../metadata/MetadataUtil.h" + + +#include "../interpreter/InterpreterModule.h" +#include "../interpreter/MethodBridge.h" +#include "../interpreter/Interpreter.h" +#include "../interpreter/MemoryUtil.h" +#include "../interpreter/InstrinctDef.h" + +using namespace hybridclr::interpreter; +using namespace hybridclr::metadata; + +//!!!{{CODE + + +//!!!}}CODE diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/UnityVersion.h.tpl b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/UnityVersion.h.tpl new file mode 100644 index 00000000..f0fddb86 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/Templates/UnityVersion.h.tpl @@ -0,0 +1,6 @@ +#pragma once + +//!!!{{UNITY_VERSION + + +//!!!}}UNITY_VERSION \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json new file mode 100644 index 00000000..afda70ec --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json @@ -0,0 +1,39 @@ +{ + "versions": [ + { + "unity_version":"2019", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2019-8.1.0"} + }, + { + "unity_version":"2020", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2020-8.1.0"} + }, + { + "unity_version":"2021", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2021-8.1.0"} + }, + { + "unity_version":"2022", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2022-8.2.0"} + }, + { + "unity_version":"2022-tuanjie", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2022-tuanjie-8.3.0"} + }, + { + "unity_version":"2023", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v2023-8.1.0"} + }, + { + "unity_version":"6000", + "hybridclr" : { "branch":"v8.3.0"}, + "il2cpp_plus": { "branch":"v6000-8.1.0"} + } + ] +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor.meta new file mode 100644 index 00000000..003696aa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3fabc41cf17c444995fc01a76c5dbe6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta new file mode 100644 index 00000000..5f5d5537 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: daa1e09af240aae4da0741843cb2b3f3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip.meta new file mode 100644 index 00000000..48fedff1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 08f44f6e8bfbc2c45afae7bdd2d7f21f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common.meta new file mode 100644 index 00000000..0e4cc9e5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5cedddbfa873eb94496b496e2a3ce8aa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs new file mode 100644 index 00000000..d03fcec1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs @@ -0,0 +1,55 @@ +// Common/CRC.cs + +namespace SevenZip +{ + public class CRC + { + public static readonly uint[] Table; + + static CRC() + { + Table = new uint[256]; + const uint kPoly = 0xEDB88320; + for (uint i = 0; i < 256; i++) + { + uint r = i; + for (int j = 0; j < 8; j++) + if ((r & 1) != 0) + r = (r >> 1) ^ kPoly; + else + r >>= 1; + Table[i] = r; + } + } + + uint _value = 0xFFFFFFFF; + + public void Init() { _value = 0xFFFFFFFF; } + + public void UpdateByte(byte b) + { + _value = Table[(((byte)(_value)) ^ b)] ^ (_value >> 8); + } + + public void Update(byte[] data, uint offset, uint size) + { + for (uint i = 0; i < size; i++) + _value = Table[(((byte)(_value)) ^ data[offset + i])] ^ (_value >> 8); + } + + public uint GetDigest() { return _value ^ 0xFFFFFFFF; } + + static uint CalculateDigest(byte[] data, uint offset, uint size) + { + CRC crc = new CRC(); + // crc.Init(); + crc.Update(data, offset, size); + return crc.GetDigest(); + } + + static bool VerifyDigest(uint digest, byte[] data, uint offset, uint size) + { + return (CalculateDigest(data, offset, size) == digest); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs.meta new file mode 100644 index 00000000..e366fdf0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CRC.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0bb58ecc915d9d47b8567adaa2d0ca6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs new file mode 100644 index 00000000..8eabf59d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs @@ -0,0 +1,274 @@ +// CommandLineParser.cs + +using System; +using System.Collections; + +namespace SevenZip.CommandLineParser +{ + public enum SwitchType + { + Simple, + PostMinus, + LimitedPostString, + UnLimitedPostString, + PostChar + } + + public class SwitchForm + { + public string IDString; + public SwitchType Type; + public bool Multi; + public int MinLen; + public int MaxLen; + public string PostCharSet; + + public SwitchForm(string idString, SwitchType type, bool multi, + int minLen, int maxLen, string postCharSet) + { + IDString = idString; + Type = type; + Multi = multi; + MinLen = minLen; + MaxLen = maxLen; + PostCharSet = postCharSet; + } + public SwitchForm(string idString, SwitchType type, bool multi, int minLen): + this(idString, type, multi, minLen, 0, "") + { + } + public SwitchForm(string idString, SwitchType type, bool multi): + this(idString, type, multi, 0) + { + } + } + + public class SwitchResult + { + public bool ThereIs; + public bool WithMinus; + public ArrayList PostStrings = new ArrayList(); + public int PostCharIndex; + public SwitchResult() + { + ThereIs = false; + } + } + + public class Parser + { + public ArrayList NonSwitchStrings = new ArrayList(); + SwitchResult[] _switches; + + public Parser(int numSwitches) + { + _switches = new SwitchResult[numSwitches]; + for (int i = 0; i < numSwitches; i++) + _switches[i] = new SwitchResult(); + } + + bool ParseString(string srcString, SwitchForm[] switchForms) + { + int len = srcString.Length; + if (len == 0) + return false; + int pos = 0; + if (!IsItSwitchChar(srcString[pos])) + return false; + while (pos < len) + { + if (IsItSwitchChar(srcString[pos])) + pos++; + const int kNoLen = -1; + int matchedSwitchIndex = 0; + int maxLen = kNoLen; + for (int switchIndex = 0; switchIndex < _switches.Length; switchIndex++) + { + int switchLen = switchForms[switchIndex].IDString.Length; + if (switchLen <= maxLen || pos + switchLen > len) + continue; + if (String.Compare(switchForms[switchIndex].IDString, 0, + srcString, pos, switchLen, true) == 0) + { + matchedSwitchIndex = switchIndex; + maxLen = switchLen; + } + } + if (maxLen == kNoLen) + throw new Exception("maxLen == kNoLen"); + SwitchResult matchedSwitch = _switches[matchedSwitchIndex]; + SwitchForm switchForm = switchForms[matchedSwitchIndex]; + if ((!switchForm.Multi) && matchedSwitch.ThereIs) + throw new Exception("switch must be single"); + matchedSwitch.ThereIs = true; + pos += maxLen; + int tailSize = len - pos; + SwitchType type = switchForm.Type; + switch (type) + { + case SwitchType.PostMinus: + { + if (tailSize == 0) + matchedSwitch.WithMinus = false; + else + { + matchedSwitch.WithMinus = (srcString[pos] == kSwitchMinus); + if (matchedSwitch.WithMinus) + pos++; + } + break; + } + case SwitchType.PostChar: + { + if (tailSize < switchForm.MinLen) + throw new Exception("switch is not full"); + string charSet = switchForm.PostCharSet; + const int kEmptyCharValue = -1; + if (tailSize == 0) + matchedSwitch.PostCharIndex = kEmptyCharValue; + else + { + int index = charSet.IndexOf(srcString[pos]); + if (index < 0) + matchedSwitch.PostCharIndex = kEmptyCharValue; + else + { + matchedSwitch.PostCharIndex = index; + pos++; + } + } + break; + } + case SwitchType.LimitedPostString: + case SwitchType.UnLimitedPostString: + { + int minLen = switchForm.MinLen; + if (tailSize < minLen) + throw new Exception("switch is not full"); + if (type == SwitchType.UnLimitedPostString) + { + matchedSwitch.PostStrings.Add(srcString.Substring(pos)); + return true; + } + String stringSwitch = srcString.Substring(pos, minLen); + pos += minLen; + for (int i = minLen; i < switchForm.MaxLen && pos < len; i++, pos++) + { + char c = srcString[pos]; + if (IsItSwitchChar(c)) + break; + stringSwitch += c; + } + matchedSwitch.PostStrings.Add(stringSwitch); + break; + } + } + } + return true; + + } + + public void ParseStrings(SwitchForm[] switchForms, string[] commandStrings) + { + int numCommandStrings = commandStrings.Length; + bool stopSwitch = false; + for (int i = 0; i < numCommandStrings; i++) + { + string s = commandStrings[i]; + if (stopSwitch) + NonSwitchStrings.Add(s); + else + if (s == kStopSwitchParsing) + stopSwitch = true; + else + if (!ParseString(s, switchForms)) + NonSwitchStrings.Add(s); + } + } + + public SwitchResult this[int index] { get { return _switches[index]; } } + + public static int ParseCommand(CommandForm[] commandForms, string commandString, + out string postString) + { + for (int i = 0; i < commandForms.Length; i++) + { + string id = commandForms[i].IDString; + if (commandForms[i].PostStringMode) + { + if (commandString.IndexOf(id) == 0) + { + postString = commandString.Substring(id.Length); + return i; + } + } + else + if (commandString == id) + { + postString = ""; + return i; + } + } + postString = ""; + return -1; + } + + static bool ParseSubCharsCommand(int numForms, CommandSubCharsSet[] forms, + string commandString, ArrayList indices) + { + indices.Clear(); + int numUsedChars = 0; + for (int i = 0; i < numForms; i++) + { + CommandSubCharsSet charsSet = forms[i]; + int currentIndex = -1; + int len = charsSet.Chars.Length; + for (int j = 0; j < len; j++) + { + char c = charsSet.Chars[j]; + int newIndex = commandString.IndexOf(c); + if (newIndex >= 0) + { + if (currentIndex >= 0) + return false; + if (commandString.IndexOf(c, newIndex + 1) >= 0) + return false; + currentIndex = j; + numUsedChars++; + } + } + if (currentIndex == -1 && !charsSet.EmptyAllowed) + return false; + indices.Add(currentIndex); + } + return (numUsedChars == commandString.Length); + } + const char kSwitchID1 = '-'; + const char kSwitchID2 = '/'; + + const char kSwitchMinus = '-'; + const string kStopSwitchParsing = "--"; + + static bool IsItSwitchChar(char c) + { + return (c == kSwitchID1 || c == kSwitchID2); + } + } + + public class CommandForm + { + public string IDString = ""; + public bool PostStringMode = false; + public CommandForm(string idString, bool postStringMode) + { + IDString = idString; + PostStringMode = postStringMode; + } + } + + class CommandSubCharsSet + { + public string Chars = ""; + public bool EmptyAllowed = false; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs.meta new file mode 100644 index 00000000..a35c2920 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/CommandLineParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc0b3921c07ef224cb3656391f4317c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs new file mode 100644 index 00000000..7c51f0bc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs @@ -0,0 +1,72 @@ +// InBuffer.cs + +namespace SevenZip.Buffer +{ + public class InBuffer + { + byte[] m_Buffer; + uint m_Pos; + uint m_Limit; + uint m_BufferSize; + System.IO.Stream m_Stream; + bool m_StreamWasExhausted; + ulong m_ProcessedSize; + + public InBuffer(uint bufferSize) + { + m_Buffer = new byte[bufferSize]; + m_BufferSize = bufferSize; + } + + public void Init(System.IO.Stream stream) + { + m_Stream = stream; + m_ProcessedSize = 0; + m_Limit = 0; + m_Pos = 0; + m_StreamWasExhausted = false; + } + + public bool ReadBlock() + { + if (m_StreamWasExhausted) + return false; + m_ProcessedSize += m_Pos; + int aNumProcessedBytes = m_Stream.Read(m_Buffer, 0, (int)m_BufferSize); + m_Pos = 0; + m_Limit = (uint)aNumProcessedBytes; + m_StreamWasExhausted = (aNumProcessedBytes == 0); + return (!m_StreamWasExhausted); + } + + + public void ReleaseStream() + { + // m_Stream.Close(); + m_Stream = null; + } + + public bool ReadByte(byte b) // check it + { + if (m_Pos >= m_Limit) + if (!ReadBlock()) + return false; + b = m_Buffer[m_Pos++]; + return true; + } + + public byte ReadByte() + { + // return (byte)m_Stream.ReadByte(); + if (m_Pos >= m_Limit) + if (!ReadBlock()) + return 0xFF; + return m_Buffer[m_Pos++]; + } + + public ulong GetProcessedSize() + { + return m_ProcessedSize + m_Pos; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs.meta new file mode 100644 index 00000000..843e5cf7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/InBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ea025f5b275726479ba55e956574898 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs new file mode 100644 index 00000000..2da16e16 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs @@ -0,0 +1,47 @@ +// OutBuffer.cs + +namespace SevenZip.Buffer +{ + public class OutBuffer + { + byte[] m_Buffer; + uint m_Pos; + uint m_BufferSize; + System.IO.Stream m_Stream; + ulong m_ProcessedSize; + + public OutBuffer(uint bufferSize) + { + m_Buffer = new byte[bufferSize]; + m_BufferSize = bufferSize; + } + + public void SetStream(System.IO.Stream stream) { m_Stream = stream; } + public void FlushStream() { m_Stream.Flush(); } + public void CloseStream() { m_Stream.Close(); } + public void ReleaseStream() { m_Stream = null; } + + public void Init() + { + m_ProcessedSize = 0; + m_Pos = 0; + } + + public void WriteByte(byte b) + { + m_Buffer[m_Pos++] = b; + if (m_Pos >= m_BufferSize) + FlushData(); + } + + public void FlushData() + { + if (m_Pos == 0) + return; + m_Stream.Write(m_Buffer, 0, (int)m_Pos); + m_Pos = 0; + } + + public ulong GetProcessedSize() { return m_ProcessedSize + m_Pos; } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs.meta new file mode 100644 index 00000000..38af8c8f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Common/OutBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d61847c152e7384ab15aea89a942f54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress.meta new file mode 100644 index 00000000..b67e6ba8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b6765bf0d03fd243b47d74c9b73c38b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ.meta new file mode 100644 index 00000000..c3034143 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a83572c42056f474db56e761a4f35e38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs new file mode 100644 index 00000000..10ca2b37 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs @@ -0,0 +1,24 @@ +// IMatchFinder.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + interface IInWindowStream + { + void SetStream(System.IO.Stream inStream); + void Init(); + void ReleaseStream(); + Byte GetIndexByte(Int32 index); + UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit); + UInt32 GetNumAvailableBytes(); + } + + interface IMatchFinder : IInWindowStream + { + void Create(UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter); + UInt32 GetMatches(UInt32[] distances); + void Skip(UInt32 num); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs.meta new file mode 100644 index 00000000..88c6d3d3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/IMatchFinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfd1ee430d8108042a2607d92d7f58ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs new file mode 100644 index 00000000..c1c006b6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs @@ -0,0 +1,367 @@ +// LzBinTree.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + public class BinTree : InWindow, IMatchFinder + { + UInt32 _cyclicBufferPos; + UInt32 _cyclicBufferSize = 0; + UInt32 _matchMaxLen; + + UInt32[] _son; + UInt32[] _hash; + + UInt32 _cutValue = 0xFF; + UInt32 _hashMask; + UInt32 _hashSizeSum = 0; + + bool HASH_ARRAY = true; + + const UInt32 kHash2Size = 1 << 10; + const UInt32 kHash3Size = 1 << 16; + const UInt32 kBT2HashSize = 1 << 16; + const UInt32 kStartMaxLen = 1; + const UInt32 kHash3Offset = kHash2Size; + const UInt32 kEmptyHashValue = 0; + const UInt32 kMaxValForNormalize = ((UInt32)1 << 31) - 1; + + UInt32 kNumHashDirectBytes = 0; + UInt32 kMinMatchCheck = 4; + UInt32 kFixHashSize = kHash2Size + kHash3Size; + + public void SetType(int numHashBytes) + { + HASH_ARRAY = (numHashBytes > 2); + if (HASH_ARRAY) + { + kNumHashDirectBytes = 0; + kMinMatchCheck = 4; + kFixHashSize = kHash2Size + kHash3Size; + } + else + { + kNumHashDirectBytes = 2; + kMinMatchCheck = 2 + 1; + kFixHashSize = 0; + } + } + + public new void SetStream(System.IO.Stream stream) { base.SetStream(stream); } + public new void ReleaseStream() { base.ReleaseStream(); } + + public new void Init() + { + base.Init(); + for (UInt32 i = 0; i < _hashSizeSum; i++) + _hash[i] = kEmptyHashValue; + _cyclicBufferPos = 0; + ReduceOffsets(-1); + } + + public new void MovePos() + { + if (++_cyclicBufferPos >= _cyclicBufferSize) + _cyclicBufferPos = 0; + base.MovePos(); + if (_pos == kMaxValForNormalize) + Normalize(); + } + + public new Byte GetIndexByte(Int32 index) { return base.GetIndexByte(index); } + + public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) + { return base.GetMatchLen(index, distance, limit); } + + public new UInt32 GetNumAvailableBytes() { return base.GetNumAvailableBytes(); } + + public void Create(UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter) + { + if (historySize > kMaxValForNormalize - 256) + throw new Exception(); + _cutValue = 16 + (matchMaxLen >> 1); + + UInt32 windowReservSize = (historySize + keepAddBufferBefore + + matchMaxLen + keepAddBufferAfter) / 2 + 256; + + base.Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize); + + _matchMaxLen = matchMaxLen; + + UInt32 cyclicBufferSize = historySize + 1; + if (_cyclicBufferSize != cyclicBufferSize) + _son = new UInt32[(_cyclicBufferSize = cyclicBufferSize) * 2]; + + UInt32 hs = kBT2HashSize; + + if (HASH_ARRAY) + { + hs = historySize - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; + if (hs > (1 << 24)) + hs >>= 1; + _hashMask = hs; + hs++; + hs += kFixHashSize; + } + if (hs != _hashSizeSum) + _hash = new UInt32[_hashSizeSum = hs]; + } + + public UInt32 GetMatches(UInt32[] distances) + { + UInt32 lenLimit; + if (_pos + _matchMaxLen <= _streamPos) + lenLimit = _matchMaxLen; + else + { + lenLimit = _streamPos - _pos; + if (lenLimit < kMinMatchCheck) + { + MovePos(); + return 0; + } + } + + UInt32 offset = 0; + UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + UInt32 cur = _bufferOffset + _pos; + UInt32 maxLen = kStartMaxLen; // to avoid items for len < hashSize; + UInt32 hashValue, hash2Value = 0, hash3Value = 0; + + if (HASH_ARRAY) + { + UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + hash2Value = temp & (kHash2Size - 1); + temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8); + hash3Value = temp & (kHash3Size - 1); + hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; + } + else + hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8); + + UInt32 curMatch = _hash[kFixHashSize + hashValue]; + if (HASH_ARRAY) + { + UInt32 curMatch2 = _hash[hash2Value]; + UInt32 curMatch3 = _hash[kHash3Offset + hash3Value]; + _hash[hash2Value] = _pos; + _hash[kHash3Offset + hash3Value] = _pos; + if (curMatch2 > matchMinPos) + if (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur]) + { + distances[offset++] = maxLen = 2; + distances[offset++] = _pos - curMatch2 - 1; + } + if (curMatch3 > matchMinPos) + if (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur]) + { + if (curMatch3 == curMatch2) + offset -= 2; + distances[offset++] = maxLen = 3; + distances[offset++] = _pos - curMatch3 - 1; + curMatch2 = curMatch3; + } + if (offset != 0 && curMatch2 == curMatch) + { + offset -= 2; + maxLen = kStartMaxLen; + } + } + + _hash[kFixHashSize + hashValue] = _pos; + + UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; + UInt32 ptr1 = (_cyclicBufferPos << 1); + + UInt32 len0, len1; + len0 = len1 = kNumHashDirectBytes; + + if (kNumHashDirectBytes != 0) + { + if (curMatch > matchMinPos) + { + if (_bufferBase[_bufferOffset + curMatch + kNumHashDirectBytes] != + _bufferBase[cur + kNumHashDirectBytes]) + { + distances[offset++] = maxLen = kNumHashDirectBytes; + distances[offset++] = _pos - curMatch - 1; + } + } + } + + UInt32 count = _cutValue; + + while(true) + { + if(curMatch <= matchMinPos || count-- == 0) + { + _son[ptr0] = _son[ptr1] = kEmptyHashValue; + break; + } + UInt32 delta = _pos - curMatch; + UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? + (_cyclicBufferPos - delta) : + (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; + + UInt32 pby1 = _bufferOffset + curMatch; + UInt32 len = Math.Min(len0, len1); + if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) + { + while(++len != lenLimit) + if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) + break; + if (maxLen < len) + { + distances[offset++] = maxLen = len; + distances[offset++] = delta - 1; + if (len == lenLimit) + { + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; + } + } + } + if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) + { + _son[ptr1] = curMatch; + ptr1 = cyclicPos + 1; + curMatch = _son[ptr1]; + len1 = len; + } + else + { + _son[ptr0] = curMatch; + ptr0 = cyclicPos; + curMatch = _son[ptr0]; + len0 = len; + } + } + MovePos(); + return offset; + } + + public void Skip(UInt32 num) + { + do + { + UInt32 lenLimit; + if (_pos + _matchMaxLen <= _streamPos) + lenLimit = _matchMaxLen; + else + { + lenLimit = _streamPos - _pos; + if (lenLimit < kMinMatchCheck) + { + MovePos(); + continue; + } + } + + UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + UInt32 cur = _bufferOffset + _pos; + + UInt32 hashValue; + + if (HASH_ARRAY) + { + UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + UInt32 hash2Value = temp & (kHash2Size - 1); + _hash[hash2Value] = _pos; + temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8); + UInt32 hash3Value = temp & (kHash3Size - 1); + _hash[kHash3Offset + hash3Value] = _pos; + hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask; + } + else + hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8); + + UInt32 curMatch = _hash[kFixHashSize + hashValue]; + _hash[kFixHashSize + hashValue] = _pos; + + UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; + UInt32 ptr1 = (_cyclicBufferPos << 1); + + UInt32 len0, len1; + len0 = len1 = kNumHashDirectBytes; + + UInt32 count = _cutValue; + while (true) + { + if (curMatch <= matchMinPos || count-- == 0) + { + _son[ptr0] = _son[ptr1] = kEmptyHashValue; + break; + } + + UInt32 delta = _pos - curMatch; + UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ? + (_cyclicBufferPos - delta) : + (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; + + UInt32 pby1 = _bufferOffset + curMatch; + UInt32 len = Math.Min(len0, len1); + if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) + { + while (++len != lenLimit) + if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) + break; + if (len == lenLimit) + { + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; + } + } + if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) + { + _son[ptr1] = curMatch; + ptr1 = cyclicPos + 1; + curMatch = _son[ptr1]; + len1 = len; + } + else + { + _son[ptr0] = curMatch; + ptr0 = cyclicPos; + curMatch = _son[ptr0]; + len0 = len; + } + } + MovePos(); + } + while (--num != 0); + } + + void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue) + { + for (UInt32 i = 0; i < numItems; i++) + { + UInt32 value = items[i]; + if (value <= subValue) + value = kEmptyHashValue; + else + value -= subValue; + items[i] = value; + } + } + + void Normalize() + { + UInt32 subValue = _pos - _cyclicBufferSize; + NormalizeLinks(_son, _cyclicBufferSize * 2, subValue); + NormalizeLinks(_hash, _hashSizeSum, subValue); + ReduceOffsets((Int32)subValue); + } + + public void SetCutValue(UInt32 cutValue) { _cutValue = cutValue; } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs.meta new file mode 100644 index 00000000..49637e87 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzBinTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ba8015d2d5df3a459615ffc06635e6d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs new file mode 100644 index 00000000..52d23ce3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs @@ -0,0 +1,132 @@ +// LzInWindow.cs + +using System; + +namespace SevenZip.Compression.LZ +{ + public class InWindow + { + public Byte[] _bufferBase = null; // pointer to buffer with data + System.IO.Stream _stream; + UInt32 _posLimit; // offset (from _buffer) of first byte when new block reading must be done + bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream + + UInt32 _pointerToLastSafePosition; + + public UInt32 _bufferOffset; + + public UInt32 _blockSize; // Size of Allocated memory block + public UInt32 _pos; // offset (from _buffer) of curent byte + UInt32 _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos + UInt32 _keepSizeAfter; // how many BYTEs must be kept buffer after _pos + public UInt32 _streamPos; // offset (from _buffer) of first not read byte from Stream + + public void MoveBlock() + { + UInt32 offset = (UInt32)(_bufferOffset) + _pos - _keepSizeBefore; + // we need one additional byte, since MovePos moves on 1 byte. + if (offset > 0) + offset--; + + UInt32 numBytes = (UInt32)(_bufferOffset) + _streamPos - offset; + + // check negative offset ???? + for (UInt32 i = 0; i < numBytes; i++) + _bufferBase[i] = _bufferBase[offset + i]; + _bufferOffset -= offset; + } + + public virtual void ReadBlock() + { + if (_streamEndWasReached) + return; + while (true) + { + int size = (int)((0 - _bufferOffset) + _blockSize - _streamPos); + if (size == 0) + return; + int numReadBytes = _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size); + if (numReadBytes == 0) + { + _posLimit = _streamPos; + UInt32 pointerToPostion = _bufferOffset + _posLimit; + if (pointerToPostion > _pointerToLastSafePosition) + _posLimit = (UInt32)(_pointerToLastSafePosition - _bufferOffset); + + _streamEndWasReached = true; + return; + } + _streamPos += (UInt32)numReadBytes; + if (_streamPos >= _pos + _keepSizeAfter) + _posLimit = _streamPos - _keepSizeAfter; + } + } + + void Free() { _bufferBase = null; } + + public void Create(UInt32 keepSizeBefore, UInt32 keepSizeAfter, UInt32 keepSizeReserv) + { + _keepSizeBefore = keepSizeBefore; + _keepSizeAfter = keepSizeAfter; + UInt32 blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv; + if (_bufferBase == null || _blockSize != blockSize) + { + Free(); + _blockSize = blockSize; + _bufferBase = new Byte[_blockSize]; + } + _pointerToLastSafePosition = _blockSize - keepSizeAfter; + } + + public void SetStream(System.IO.Stream stream) { _stream = stream; } + public void ReleaseStream() { _stream = null; } + + public void Init() + { + _bufferOffset = 0; + _pos = 0; + _streamPos = 0; + _streamEndWasReached = false; + ReadBlock(); + } + + public void MovePos() + { + _pos++; + if (_pos > _posLimit) + { + UInt32 pointerToPostion = _bufferOffset + _pos; + if (pointerToPostion > _pointerToLastSafePosition) + MoveBlock(); + ReadBlock(); + } + } + + public Byte GetIndexByte(Int32 index) { return _bufferBase[_bufferOffset + _pos + index]; } + + // index + limit have not to exceed _keepSizeAfter; + public UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) + { + if (_streamEndWasReached) + if ((_pos + index) + limit > _streamPos) + limit = _streamPos - (UInt32)(_pos + index); + distance++; + // Byte *pby = _buffer + (size_t)_pos + index; + UInt32 pby = _bufferOffset + _pos + (UInt32)index; + + UInt32 i; + for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++); + return i; + } + + public UInt32 GetNumAvailableBytes() { return _streamPos - _pos; } + + public void ReduceOffsets(Int32 subValue) + { + _bufferOffset += (UInt32)subValue; + _posLimit -= (UInt32)subValue; + _pos -= (UInt32)subValue; + _streamPos -= (UInt32)subValue; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs.meta new file mode 100644 index 00000000..f1e45d68 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzInWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 333112c3785a6ec4cbbfec8515081cac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs new file mode 100644 index 00000000..c998584a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs @@ -0,0 +1,110 @@ +// LzOutWindow.cs + +namespace SevenZip.Compression.LZ +{ + public class OutWindow + { + byte[] _buffer = null; + uint _pos; + uint _windowSize = 0; + uint _streamPos; + System.IO.Stream _stream; + + public uint TrainSize = 0; + + public void Create(uint windowSize) + { + if (_windowSize != windowSize) + { + // System.GC.Collect(); + _buffer = new byte[windowSize]; + } + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + } + + public void Init(System.IO.Stream stream, bool solid) + { + ReleaseStream(); + _stream = stream; + if (!solid) + { + _streamPos = 0; + _pos = 0; + TrainSize = 0; + } + } + + public bool Train(System.IO.Stream stream) + { + long len = stream.Length; + uint size = (len < _windowSize) ? (uint)len : _windowSize; + TrainSize = size; + stream.Position = len - size; + _streamPos = _pos = 0; + while (size > 0) + { + uint curSize = _windowSize - _pos; + if (size < curSize) + curSize = size; + int numReadBytes = stream.Read(_buffer, (int)_pos, (int)curSize); + if (numReadBytes == 0) + return false; + size -= (uint)numReadBytes; + _pos += (uint)numReadBytes; + _streamPos += (uint)numReadBytes; + if (_pos == _windowSize) + _streamPos = _pos = 0; + } + return true; + } + + public void ReleaseStream() + { + Flush(); + _stream = null; + } + + public void Flush() + { + uint size = _pos - _streamPos; + if (size == 0) + return; + _stream.Write(_buffer, (int)_streamPos, (int)size); + if (_pos >= _windowSize) + _pos = 0; + _streamPos = _pos; + } + + public void CopyBlock(uint distance, uint len) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + for (; len > 0; len--) + { + if (pos >= _windowSize) + pos = 0; + _buffer[_pos++] = _buffer[pos++]; + if (_pos >= _windowSize) + Flush(); + } + } + + public void PutByte(byte b) + { + _buffer[_pos++] = b; + if (_pos >= _windowSize) + Flush(); + } + + public byte GetByte(uint distance) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + return _buffer[pos]; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs.meta new file mode 100644 index 00000000..7b3f5340 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZ/LzOutWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb296a6e14ab7c44cb6388693d581a62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA.meta new file mode 100644 index 00000000..5c459683 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5fcd2f5c8b724164a8a01cee73896d1a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs new file mode 100644 index 00000000..c7bca86f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs @@ -0,0 +1,76 @@ +// LzmaBase.cs + +namespace SevenZip.Compression.LZMA +{ + internal abstract class Base + { + public const uint kNumRepDistances = 4; + public const uint kNumStates = 12; + + // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; + // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; + // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; + // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + + public struct State + { + public uint Index; + public void Init() { Index = 0; } + public void UpdateChar() + { + if (Index < 4) Index = 0; + else if (Index < 10) Index -= 3; + else Index -= 6; + } + public void UpdateMatch() { Index = (uint)(Index < 7 ? 7 : 10); } + public void UpdateRep() { Index = (uint)(Index < 7 ? 8 : 11); } + public void UpdateShortRep() { Index = (uint)(Index < 7 ? 9 : 11); } + public bool IsCharState() { return Index < 7; } + } + + public const int kNumPosSlotBits = 6; + public const int kDicLogSizeMin = 0; + // public const int kDicLogSizeMax = 30; + // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; + + public const int kNumLenToPosStatesBits = 2; // it's for speed optimization + public const uint kNumLenToPosStates = 1 << kNumLenToPosStatesBits; + + public const uint kMatchMinLen = 2; + + public static uint GetLenToPosState(uint len) + { + len -= kMatchMinLen; + if (len < kNumLenToPosStates) + return len; + return (uint)(kNumLenToPosStates - 1); + } + + public const int kNumAlignBits = 4; + public const uint kAlignTableSize = 1 << kNumAlignBits; + public const uint kAlignMask = (kAlignTableSize - 1); + + public const uint kStartPosModelIndex = 4; + public const uint kEndPosModelIndex = 14; + public const uint kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; + + public const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2); + + public const uint kNumLitPosStatesBitsEncodingMax = 4; + public const uint kNumLitContextBitsMax = 8; + + public const int kNumPosStatesBitsMax = 4; + public const uint kNumPosStatesMax = (1 << kNumPosStatesBitsMax); + public const int kNumPosStatesBitsEncodingMax = 4; + public const uint kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax); + + public const int kNumLowLenBits = 3; + public const int kNumMidLenBits = 3; + public const int kNumHighLenBits = 8; + public const uint kNumLowLenSymbols = 1 << kNumLowLenBits; + public const uint kNumMidLenSymbols = 1 << kNumMidLenBits; + public const uint kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + + (1 << kNumHighLenBits); + public const uint kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs.meta new file mode 100644 index 00000000..10b16edb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62f365191077342479f8227edadf57e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs new file mode 100644 index 00000000..a9be39fc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs @@ -0,0 +1,398 @@ +// LzmaDecoder.cs + +using System; + +namespace SevenZip.Compression.LZMA +{ + using RangeCoder; + + public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream + { + class LenDecoder + { + BitDecoder m_Choice = new BitDecoder(); + BitDecoder m_Choice2 = new BitDecoder(); + BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder m_HighCoder = new BitTreeDecoder(Base.kNumHighLenBits); + uint m_NumPosStates = 0; + + public void Create(uint numPosStates) + { + for (uint posState = m_NumPosStates; posState < numPosStates; posState++) + { + m_LowCoder[posState] = new BitTreeDecoder(Base.kNumLowLenBits); + m_MidCoder[posState] = new BitTreeDecoder(Base.kNumMidLenBits); + } + m_NumPosStates = numPosStates; + } + + public void Init() + { + m_Choice.Init(); + for (uint posState = 0; posState < m_NumPosStates; posState++) + { + m_LowCoder[posState].Init(); + m_MidCoder[posState].Init(); + } + m_Choice2.Init(); + m_HighCoder.Init(); + } + + public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) + { + if (m_Choice.Decode(rangeDecoder) == 0) + return m_LowCoder[posState].Decode(rangeDecoder); + else + { + uint symbol = Base.kNumLowLenSymbols; + if (m_Choice2.Decode(rangeDecoder) == 0) + symbol += m_MidCoder[posState].Decode(rangeDecoder); + else + { + symbol += Base.kNumMidLenSymbols; + symbol += m_HighCoder.Decode(rangeDecoder); + } + return symbol; + } + } + } + + class LiteralDecoder + { + struct Decoder2 + { + BitDecoder[] m_Decoders; + public void Create() { m_Decoders = new BitDecoder[0x300]; } + public void Init() { for (int i = 0; i < 0x300; i++) m_Decoders[i].Init(); } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) + { + uint symbol = 1; + do + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + while (symbol < 0x100); + return (byte)symbol; + } + + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte) + { + uint symbol = 1; + do + { + uint matchBit = (uint)(matchByte >> 7) & 1; + matchByte <<= 1; + uint bit = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + break; + } + } + while (symbol < 0x100); + return (byte)symbol; + } + } + + Decoder2[] m_Coders; + int m_NumPrevBits; + int m_NumPosBits; + uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && + m_NumPosBits == numPosBits) + return; + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Decoder2[numStates]; + for (uint i = 0; i < numStates; i++) + m_Coders[i].Create(); + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + m_Coders[i].Init(); + } + + uint GetState(uint pos, byte prevByte) + { return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits)); } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) + { return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); } + + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) + { return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); } + }; + + LZ.OutWindow m_OutWindow = new LZ.OutWindow(); + RangeCoder.Decoder m_RangeDecoder = new RangeCoder.Decoder(); + + BitDecoder[] m_IsMatchDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + BitDecoder[] m_IsRepDecoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG0Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG1Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG2Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates]; + BitDecoder[] m_PosDecoders = new BitDecoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; + + BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(Base.kNumAlignBits); + + LenDecoder m_LenDecoder = new LenDecoder(); + LenDecoder m_RepLenDecoder = new LenDecoder(); + + LiteralDecoder m_LiteralDecoder = new LiteralDecoder(); + + uint m_DictionarySize; + uint m_DictionarySizeCheck; + + uint m_PosStateMask; + + public Decoder() + { + m_DictionarySize = 0xFFFFFFFF; + for (int i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits); + } + + void SetDictionarySize(uint dictionarySize) + { + if (m_DictionarySize != dictionarySize) + { + m_DictionarySize = dictionarySize; + m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1); + uint blockSize = Math.Max(m_DictionarySizeCheck, (1 << 12)); + m_OutWindow.Create(blockSize); + } + } + + void SetLiteralProperties(int lp, int lc) + { + if (lp > 8) + throw new InvalidParamException(); + if (lc > 8) + throw new InvalidParamException(); + m_LiteralDecoder.Create(lp, lc); + } + + void SetPosBitsProperties(int pb) + { + if (pb > Base.kNumPosStatesBitsMax) + throw new InvalidParamException(); + uint numPosStates = (uint)1 << pb; + m_LenDecoder.Create(numPosStates); + m_RepLenDecoder.Create(numPosStates); + m_PosStateMask = numPosStates - 1; + } + + bool _solid = false; + void Init(System.IO.Stream inStream, System.IO.Stream outStream) + { + m_RangeDecoder.Init(inStream); + m_OutWindow.Init(outStream, _solid); + + uint i; + for (i = 0; i < Base.kNumStates; i++) + { + for (uint j = 0; j <= m_PosStateMask; j++) + { + uint index = (i << Base.kNumPosStatesBitsMax) + j; + m_IsMatchDecoders[index].Init(); + m_IsRep0LongDecoders[index].Init(); + } + m_IsRepDecoders[i].Init(); + m_IsRepG0Decoders[i].Init(); + m_IsRepG1Decoders[i].Init(); + m_IsRepG2Decoders[i].Init(); + } + + m_LiteralDecoder.Init(); + for (i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i].Init(); + // m_PosSpecDecoder.Init(); + for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) + m_PosDecoders[i].Init(); + + m_LenDecoder.Init(); + m_RepLenDecoder.Init(); + m_PosAlignDecoder.Init(); + } + + public void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress) + { + Init(inStream, outStream); + + Base.State state = new Base.State(); + state.Init(); + uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; + + UInt64 nowPos64 = 0; + UInt64 outSize64 = (UInt64)outSize; + if (nowPos64 < outSize64) + { + if (m_IsMatchDecoders[state.Index << Base.kNumPosStatesBitsMax].Decode(m_RangeDecoder) != 0) + throw new DataErrorException(); + state.UpdateChar(); + byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0); + m_OutWindow.PutByte(b); + nowPos64++; + } + while (nowPos64 < outSize64) + { + // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); + // while(nowPos64 < next) + { + uint posState = (uint)nowPos64 & m_PosStateMask; + if (m_IsMatchDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + byte b; + byte prevByte = m_OutWindow.GetByte(0); + if (!state.IsCharState()) + b = m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, + (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0)); + else + b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte); + m_OutWindow.PutByte(b); + state.UpdateChar(); + nowPos64++; + } + else + { + uint len; + if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) + { + if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + if (m_IsRep0LongDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + state.UpdateShortRep(); + m_OutWindow.PutByte(m_OutWindow.GetByte(rep0)); + nowPos64++; + continue; + } + } + else + { + UInt32 distance; + if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + distance = rep1; + } + else + { + if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) + distance = rep2; + else + { + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen; + state.UpdateRep(); + } + else + { + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); + state.UpdateMatch(); + uint posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder); + if (posSlot >= Base.kStartPosModelIndex) + { + int numDirectBits = (int)((posSlot >> 1) - 1); + rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < Base.kEndPosModelIndex) + rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, + rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); + else + { + rep0 += (m_RangeDecoder.DecodeDirectBits( + numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits); + rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); + } + } + else + rep0 = posSlot; + } + if (rep0 >= m_OutWindow.TrainSize + nowPos64 || rep0 >= m_DictionarySizeCheck) + { + if (rep0 == 0xFFFFFFFF) + break; + throw new DataErrorException(); + } + m_OutWindow.CopyBlock(rep0, len); + nowPos64 += len; + } + } + } + m_OutWindow.Flush(); + m_OutWindow.ReleaseStream(); + m_RangeDecoder.ReleaseStream(); + } + + public void SetDecoderProperties(byte[] properties) + { + if (properties.Length < 5) + throw new InvalidParamException(); + int lc = properties[0] % 9; + int remainder = properties[0] / 9; + int lp = remainder % 5; + int pb = remainder / 5; + if (pb > Base.kNumPosStatesBitsMax) + throw new InvalidParamException(); + UInt32 dictionarySize = 0; + for (int i = 0; i < 4; i++) + dictionarySize += ((UInt32)(properties[1 + i])) << (i * 8); + SetDictionarySize(dictionarySize); + SetLiteralProperties(lp, lc); + SetPosBitsProperties(pb); + } + + public bool Train(System.IO.Stream stream) + { + _solid = true; + return m_OutWindow.Train(stream); + } + + /* + public override bool CanRead { get { return true; }} + public override bool CanWrite { get { return true; }} + public override bool CanSeek { get { return true; }} + public override long Length { get { return 0; }} + public override long Position + { + get { return 0; } + set { } + } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + public override void Write(byte[] buffer, int offset, int count) + { + } + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + return 0; + } + public override void SetLength(long value) {} + */ + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs.meta new file mode 100644 index 00000000..27251675 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaDecoder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c60a9ce2c6df6774498e337ad69a356b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs new file mode 100644 index 00000000..0237c51f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs @@ -0,0 +1,1480 @@ +// LzmaEncoder.cs + +using System; + +namespace SevenZip.Compression.LZMA +{ + using RangeCoder; + + public class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties + { + enum EMatchFinderType + { + BT2, + BT4, + }; + + const UInt32 kIfinityPrice = 0xFFFFFFF; + + static Byte[] g_FastPos = new Byte[1 << 11]; + + static Encoder() + { + const Byte kFastSlots = 22; + int c = 2; + g_FastPos[0] = 0; + g_FastPos[1] = 1; + for (Byte slotFast = 2; slotFast < kFastSlots; slotFast++) + { + UInt32 k = ((UInt32)1 << ((slotFast >> 1) - 1)); + for (UInt32 j = 0; j < k; j++, c++) + g_FastPos[c] = slotFast; + } + } + + static UInt32 GetPosSlot(UInt32 pos) + { + if (pos < (1 << 11)) + return g_FastPos[pos]; + if (pos < (1 << 21)) + return (UInt32)(g_FastPos[pos >> 10] + 20); + return (UInt32)(g_FastPos[pos >> 20] + 40); + } + + static UInt32 GetPosSlot2(UInt32 pos) + { + if (pos < (1 << 17)) + return (UInt32)(g_FastPos[pos >> 6] + 12); + if (pos < (1 << 27)) + return (UInt32)(g_FastPos[pos >> 16] + 32); + return (UInt32)(g_FastPos[pos >> 26] + 52); + } + + Base.State _state = new Base.State(); + Byte _previousByte; + UInt32[] _repDistances = new UInt32[Base.kNumRepDistances]; + + void BaseInit() + { + _state.Init(); + _previousByte = 0; + for (UInt32 i = 0; i < Base.kNumRepDistances; i++) + _repDistances[i] = 0; + } + + const int kDefaultDictionaryLogSize = 22; + const UInt32 kNumFastBytesDefault = 0x20; + + class LiteralEncoder + { + public struct Encoder2 + { + BitEncoder[] m_Encoders; + + public void Create() { m_Encoders = new BitEncoder[0x300]; } + + public void Init() { for (int i = 0; i < 0x300; i++) m_Encoders[i].Init(); } + + public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol) + { + uint context = 1; + for (int i = 7; i >= 0; i--) + { + uint bit = (uint)((symbol >> i) & 1); + m_Encoders[context].Encode(rangeEncoder, bit); + context = (context << 1) | bit; + } + } + + public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) + { + uint context = 1; + bool same = true; + for (int i = 7; i >= 0; i--) + { + uint bit = (uint)((symbol >> i) & 1); + uint state = context; + if (same) + { + uint matchBit = (uint)((matchByte >> i) & 1); + state += ((1 + matchBit) << 8); + same = (matchBit == bit); + } + m_Encoders[state].Encode(rangeEncoder, bit); + context = (context << 1) | bit; + } + } + + public uint GetPrice(bool matchMode, byte matchByte, byte symbol) + { + uint price = 0; + uint context = 1; + int i = 7; + if (matchMode) + { + for (; i >= 0; i--) + { + uint matchBit = (uint)(matchByte >> i) & 1; + uint bit = (uint)(symbol >> i) & 1; + price += m_Encoders[((1 + matchBit) << 8) + context].GetPrice(bit); + context = (context << 1) | bit; + if (matchBit != bit) + { + i--; + break; + } + } + } + for (; i >= 0; i--) + { + uint bit = (uint)(symbol >> i) & 1; + price += m_Encoders[context].GetPrice(bit); + context = (context << 1) | bit; + } + return price; + } + } + + Encoder2[] m_Coders; + int m_NumPrevBits; + int m_NumPosBits; + uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits) + return; + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Encoder2[numStates]; + for (uint i = 0; i < numStates; i++) + m_Coders[i].Create(); + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + m_Coders[i].Init(); + } + + public Encoder2 GetSubCoder(UInt32 pos, Byte prevByte) + { return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits))]; } + } + + class LenEncoder + { + RangeCoder.BitEncoder _choice = new RangeCoder.BitEncoder(); + RangeCoder.BitEncoder _choice2 = new RangeCoder.BitEncoder(); + RangeCoder.BitTreeEncoder[] _lowCoder = new RangeCoder.BitTreeEncoder[Base.kNumPosStatesEncodingMax]; + RangeCoder.BitTreeEncoder[] _midCoder = new RangeCoder.BitTreeEncoder[Base.kNumPosStatesEncodingMax]; + RangeCoder.BitTreeEncoder _highCoder = new RangeCoder.BitTreeEncoder(Base.kNumHighLenBits); + + public LenEncoder() + { + for (UInt32 posState = 0; posState < Base.kNumPosStatesEncodingMax; posState++) + { + _lowCoder[posState] = new RangeCoder.BitTreeEncoder(Base.kNumLowLenBits); + _midCoder[posState] = new RangeCoder.BitTreeEncoder(Base.kNumMidLenBits); + } + } + + public void Init(UInt32 numPosStates) + { + _choice.Init(); + _choice2.Init(); + for (UInt32 posState = 0; posState < numPosStates; posState++) + { + _lowCoder[posState].Init(); + _midCoder[posState].Init(); + } + _highCoder.Init(); + } + + public void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) + { + if (symbol < Base.kNumLowLenSymbols) + { + _choice.Encode(rangeEncoder, 0); + _lowCoder[posState].Encode(rangeEncoder, symbol); + } + else + { + symbol -= Base.kNumLowLenSymbols; + _choice.Encode(rangeEncoder, 1); + if (symbol < Base.kNumMidLenSymbols) + { + _choice2.Encode(rangeEncoder, 0); + _midCoder[posState].Encode(rangeEncoder, symbol); + } + else + { + _choice2.Encode(rangeEncoder, 1); + _highCoder.Encode(rangeEncoder, symbol - Base.kNumMidLenSymbols); + } + } + } + + public void SetPrices(UInt32 posState, UInt32 numSymbols, UInt32[] prices, UInt32 st) + { + UInt32 a0 = _choice.GetPrice0(); + UInt32 a1 = _choice.GetPrice1(); + UInt32 b0 = a1 + _choice2.GetPrice0(); + UInt32 b1 = a1 + _choice2.GetPrice1(); + UInt32 i = 0; + for (i = 0; i < Base.kNumLowLenSymbols; i++) + { + if (i >= numSymbols) + return; + prices[st + i] = a0 + _lowCoder[posState].GetPrice(i); + } + for (; i < Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; i++) + { + if (i >= numSymbols) + return; + prices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.kNumLowLenSymbols); + } + for (; i < numSymbols; i++) + prices[st + i] = b1 + _highCoder.GetPrice(i - Base.kNumLowLenSymbols - Base.kNumMidLenSymbols); + } + }; + + const UInt32 kNumLenSpecSymbols = Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; + + class LenPriceTableEncoder : LenEncoder + { + UInt32[] _prices = new UInt32[Base.kNumLenSymbols << Base.kNumPosStatesBitsEncodingMax]; + UInt32 _tableSize; + UInt32[] _counters = new UInt32[Base.kNumPosStatesEncodingMax]; + + public void SetTableSize(UInt32 tableSize) { _tableSize = tableSize; } + + public UInt32 GetPrice(UInt32 symbol, UInt32 posState) + { + return _prices[posState * Base.kNumLenSymbols + symbol]; + } + + void UpdateTable(UInt32 posState) + { + SetPrices(posState, _tableSize, _prices, posState * Base.kNumLenSymbols); + _counters[posState] = _tableSize; + } + + public void UpdateTables(UInt32 numPosStates) + { + for (UInt32 posState = 0; posState < numPosStates; posState++) + UpdateTable(posState); + } + + public new void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) + { + base.Encode(rangeEncoder, symbol, posState); + if (--_counters[posState] == 0) + UpdateTable(posState); + } + } + + const UInt32 kNumOpts = 1 << 12; + class Optimal + { + public Base.State State; + + public bool Prev1IsChar; + public bool Prev2; + + public UInt32 PosPrev2; + public UInt32 BackPrev2; + + public UInt32 Price; + public UInt32 PosPrev; + public UInt32 BackPrev; + + public UInt32 Backs0; + public UInt32 Backs1; + public UInt32 Backs2; + public UInt32 Backs3; + + public void MakeAsChar() { BackPrev = 0xFFFFFFFF; Prev1IsChar = false; } + public void MakeAsShortRep() { BackPrev = 0; ; Prev1IsChar = false; } + public bool IsShortRep() { return (BackPrev == 0); } + }; + Optimal[] _optimum = new Optimal[kNumOpts]; + LZ.IMatchFinder _matchFinder = null; + RangeCoder.Encoder _rangeEncoder = new RangeCoder.Encoder(); + + RangeCoder.BitEncoder[] _isMatch = new RangeCoder.BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + RangeCoder.BitEncoder[] _isRep = new RangeCoder.BitEncoder[Base.kNumStates]; + RangeCoder.BitEncoder[] _isRepG0 = new RangeCoder.BitEncoder[Base.kNumStates]; + RangeCoder.BitEncoder[] _isRepG1 = new RangeCoder.BitEncoder[Base.kNumStates]; + RangeCoder.BitEncoder[] _isRepG2 = new RangeCoder.BitEncoder[Base.kNumStates]; + RangeCoder.BitEncoder[] _isRep0Long = new RangeCoder.BitEncoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + RangeCoder.BitTreeEncoder[] _posSlotEncoder = new RangeCoder.BitTreeEncoder[Base.kNumLenToPosStates]; + + RangeCoder.BitEncoder[] _posEncoders = new RangeCoder.BitEncoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; + RangeCoder.BitTreeEncoder _posAlignEncoder = new RangeCoder.BitTreeEncoder(Base.kNumAlignBits); + + LenPriceTableEncoder _lenEncoder = new LenPriceTableEncoder(); + LenPriceTableEncoder _repMatchLenEncoder = new LenPriceTableEncoder(); + + LiteralEncoder _literalEncoder = new LiteralEncoder(); + + UInt32[] _matchDistances = new UInt32[Base.kMatchMaxLen * 2 + 2]; + + UInt32 _numFastBytes = kNumFastBytesDefault; + UInt32 _longestMatchLength; + UInt32 _numDistancePairs; + + UInt32 _additionalOffset; + + UInt32 _optimumEndIndex; + UInt32 _optimumCurrentIndex; + + bool _longestMatchWasFound; + + UInt32[] _posSlotPrices = new UInt32[1 << (Base.kNumPosSlotBits + Base.kNumLenToPosStatesBits)]; + UInt32[] _distancesPrices = new UInt32[Base.kNumFullDistances << Base.kNumLenToPosStatesBits]; + UInt32[] _alignPrices = new UInt32[Base.kAlignTableSize]; + UInt32 _alignPriceCount; + + UInt32 _distTableSize = (kDefaultDictionaryLogSize * 2); + + int _posStateBits = 2; + UInt32 _posStateMask = (4 - 1); + int _numLiteralPosStateBits = 0; + int _numLiteralContextBits = 3; + + UInt32 _dictionarySize = (1 << kDefaultDictionaryLogSize); + UInt32 _dictionarySizePrev = 0xFFFFFFFF; + UInt32 _numFastBytesPrev = 0xFFFFFFFF; + + Int64 nowPos64; + bool _finished; + System.IO.Stream _inStream; + + EMatchFinderType _matchFinderType = EMatchFinderType.BT4; + bool _writeEndMark = false; + + bool _needReleaseMFStream; + + void Create() + { + if (_matchFinder == null) + { + LZ.BinTree bt = new LZ.BinTree(); + int numHashBytes = 4; + if (_matchFinderType == EMatchFinderType.BT2) + numHashBytes = 2; + bt.SetType(numHashBytes); + _matchFinder = bt; + } + _literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits); + + if (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes) + return; + _matchFinder.Create(_dictionarySize, kNumOpts, _numFastBytes, Base.kMatchMaxLen + 1); + _dictionarySizePrev = _dictionarySize; + _numFastBytesPrev = _numFastBytes; + } + + public Encoder() + { + for (int i = 0; i < kNumOpts; i++) + _optimum[i] = new Optimal(); + for (int i = 0; i < Base.kNumLenToPosStates; i++) + _posSlotEncoder[i] = new RangeCoder.BitTreeEncoder(Base.kNumPosSlotBits); + } + + void SetWriteEndMarkerMode(bool writeEndMarker) + { + _writeEndMark = writeEndMarker; + } + + void Init() + { + BaseInit(); + _rangeEncoder.Init(); + + uint i; + for (i = 0; i < Base.kNumStates; i++) + { + for (uint j = 0; j <= _posStateMask; j++) + { + uint complexState = (i << Base.kNumPosStatesBitsMax) + j; + _isMatch[complexState].Init(); + _isRep0Long[complexState].Init(); + } + _isRep[i].Init(); + _isRepG0[i].Init(); + _isRepG1[i].Init(); + _isRepG2[i].Init(); + } + _literalEncoder.Init(); + for (i = 0; i < Base.kNumLenToPosStates; i++) + _posSlotEncoder[i].Init(); + for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) + _posEncoders[i].Init(); + + _lenEncoder.Init((UInt32)1 << _posStateBits); + _repMatchLenEncoder.Init((UInt32)1 << _posStateBits); + + _posAlignEncoder.Init(); + + _longestMatchWasFound = false; + _optimumEndIndex = 0; + _optimumCurrentIndex = 0; + _additionalOffset = 0; + } + + void ReadMatchDistances(out UInt32 lenRes, out UInt32 numDistancePairs) + { + lenRes = 0; + numDistancePairs = _matchFinder.GetMatches(_matchDistances); + if (numDistancePairs > 0) + { + lenRes = _matchDistances[numDistancePairs - 2]; + if (lenRes == _numFastBytes) + lenRes += _matchFinder.GetMatchLen((int)lenRes - 1, _matchDistances[numDistancePairs - 1], + Base.kMatchMaxLen - lenRes); + } + _additionalOffset++; + } + + + void MovePos(UInt32 num) + { + if (num > 0) + { + _matchFinder.Skip(num); + _additionalOffset += num; + } + } + + UInt32 GetRepLen1Price(Base.State state, UInt32 posState) + { + return _isRepG0[state.Index].GetPrice0() + + _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0(); + } + + UInt32 GetPureRepPrice(UInt32 repIndex, Base.State state, UInt32 posState) + { + UInt32 price; + if (repIndex == 0) + { + price = _isRepG0[state.Index].GetPrice0(); + price += _isRep0Long[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + } + else + { + price = _isRepG0[state.Index].GetPrice1(); + if (repIndex == 1) + price += _isRepG1[state.Index].GetPrice0(); + else + { + price += _isRepG1[state.Index].GetPrice1(); + price += _isRepG2[state.Index].GetPrice(repIndex - 2); + } + } + return price; + } + + UInt32 GetRepPrice(UInt32 repIndex, UInt32 len, Base.State state, UInt32 posState) + { + UInt32 price = _repMatchLenEncoder.GetPrice(len - Base.kMatchMinLen, posState); + return price + GetPureRepPrice(repIndex, state, posState); + } + + UInt32 GetPosLenPrice(UInt32 pos, UInt32 len, UInt32 posState) + { + UInt32 price; + UInt32 lenToPosState = Base.GetLenToPosState(len); + if (pos < Base.kNumFullDistances) + price = _distancesPrices[(lenToPosState * Base.kNumFullDistances) + pos]; + else + price = _posSlotPrices[(lenToPosState << Base.kNumPosSlotBits) + GetPosSlot2(pos)] + + _alignPrices[pos & Base.kAlignMask]; + return price + _lenEncoder.GetPrice(len - Base.kMatchMinLen, posState); + } + + UInt32 Backward(out UInt32 backRes, UInt32 cur) + { + _optimumEndIndex = cur; + UInt32 posMem = _optimum[cur].PosPrev; + UInt32 backMem = _optimum[cur].BackPrev; + do + { + if (_optimum[cur].Prev1IsChar) + { + _optimum[posMem].MakeAsChar(); + _optimum[posMem].PosPrev = posMem - 1; + if (_optimum[cur].Prev2) + { + _optimum[posMem - 1].Prev1IsChar = false; + _optimum[posMem - 1].PosPrev = _optimum[cur].PosPrev2; + _optimum[posMem - 1].BackPrev = _optimum[cur].BackPrev2; + } + } + UInt32 posPrev = posMem; + UInt32 backCur = backMem; + + backMem = _optimum[posPrev].BackPrev; + posMem = _optimum[posPrev].PosPrev; + + _optimum[posPrev].BackPrev = backCur; + _optimum[posPrev].PosPrev = cur; + cur = posPrev; + } + while (cur > 0); + backRes = _optimum[0].BackPrev; + _optimumCurrentIndex = _optimum[0].PosPrev; + return _optimumCurrentIndex; + } + + UInt32[] reps = new UInt32[Base.kNumRepDistances]; + UInt32[] repLens = new UInt32[Base.kNumRepDistances]; + + + UInt32 GetOptimum(UInt32 position, out UInt32 backRes) + { + if (_optimumEndIndex != _optimumCurrentIndex) + { + UInt32 lenRes = _optimum[_optimumCurrentIndex].PosPrev - _optimumCurrentIndex; + backRes = _optimum[_optimumCurrentIndex].BackPrev; + _optimumCurrentIndex = _optimum[_optimumCurrentIndex].PosPrev; + return lenRes; + } + _optimumCurrentIndex = _optimumEndIndex = 0; + + UInt32 lenMain, numDistancePairs; + if (!_longestMatchWasFound) + { + ReadMatchDistances(out lenMain, out numDistancePairs); + } + else + { + lenMain = _longestMatchLength; + numDistancePairs = _numDistancePairs; + _longestMatchWasFound = false; + } + + UInt32 numAvailableBytes = _matchFinder.GetNumAvailableBytes() + 1; + if (numAvailableBytes < 2) + { + backRes = 0xFFFFFFFF; + return 1; + } + if (numAvailableBytes > Base.kMatchMaxLen) + numAvailableBytes = Base.kMatchMaxLen; + + UInt32 repMaxIndex = 0; + UInt32 i; + for (i = 0; i < Base.kNumRepDistances; i++) + { + reps[i] = _repDistances[i]; + repLens[i] = _matchFinder.GetMatchLen(0 - 1, reps[i], Base.kMatchMaxLen); + if (repLens[i] > repLens[repMaxIndex]) + repMaxIndex = i; + } + if (repLens[repMaxIndex] >= _numFastBytes) + { + backRes = repMaxIndex; + UInt32 lenRes = repLens[repMaxIndex]; + MovePos(lenRes - 1); + return lenRes; + } + + if (lenMain >= _numFastBytes) + { + backRes = _matchDistances[numDistancePairs - 1] + Base.kNumRepDistances; + MovePos(lenMain - 1); + return lenMain; + } + + Byte currentByte = _matchFinder.GetIndexByte(0 - 1); + Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - 1)); + + if (lenMain < 2 && currentByte != matchByte && repLens[repMaxIndex] < 2) + { + backRes = (UInt32)0xFFFFFFFF; + return 1; + } + + _optimum[0].State = _state; + + UInt32 posState = (position & _posStateMask); + + _optimum[1].Price = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + + _literalEncoder.GetSubCoder(position, _previousByte).GetPrice(!_state.IsCharState(), matchByte, currentByte); + _optimum[1].MakeAsChar(); + + UInt32 matchPrice = _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + UInt32 repMatchPrice = matchPrice + _isRep[_state.Index].GetPrice1(); + + if (matchByte == currentByte) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState); + if (shortRepPrice < _optimum[1].Price) + { + _optimum[1].Price = shortRepPrice; + _optimum[1].MakeAsShortRep(); + } + } + + UInt32 lenEnd = ((lenMain >= repLens[repMaxIndex]) ? lenMain : repLens[repMaxIndex]); + + if(lenEnd < 2) + { + backRes = _optimum[1].BackPrev; + return 1; + } + + _optimum[1].PosPrev = 0; + + _optimum[0].Backs0 = reps[0]; + _optimum[0].Backs1 = reps[1]; + _optimum[0].Backs2 = reps[2]; + _optimum[0].Backs3 = reps[3]; + + UInt32 len = lenEnd; + do + _optimum[len--].Price = kIfinityPrice; + while (len >= 2); + + for (i = 0; i < Base.kNumRepDistances; i++) + { + UInt32 repLen = repLens[i]; + if (repLen < 2) + continue; + UInt32 price = repMatchPrice + GetPureRepPrice(i, _state, posState); + do + { + UInt32 curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState); + Optimal optimum = _optimum[repLen]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = 0; + optimum.BackPrev = i; + optimum.Prev1IsChar = false; + } + } + while (--repLen >= 2); + } + + UInt32 normalMatchPrice = matchPrice + _isRep[_state.Index].GetPrice0(); + + len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2); + if (len <= lenMain) + { + UInt32 offs = 0; + while (len > _matchDistances[offs]) + offs += 2; + for (; ; len++) + { + UInt32 distance = _matchDistances[offs + 1]; + UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState); + Optimal optimum = _optimum[len]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = 0; + optimum.BackPrev = distance + Base.kNumRepDistances; + optimum.Prev1IsChar = false; + } + if (len == _matchDistances[offs]) + { + offs += 2; + if (offs == numDistancePairs) + break; + } + } + } + + UInt32 cur = 0; + + while (true) + { + cur++; + if (cur == lenEnd) + return Backward(out backRes, cur); + UInt32 newLen; + ReadMatchDistances(out newLen, out numDistancePairs); + if (newLen >= _numFastBytes) + { + _numDistancePairs = numDistancePairs; + _longestMatchLength = newLen; + _longestMatchWasFound = true; + return Backward(out backRes, cur); + } + position++; + UInt32 posPrev = _optimum[cur].PosPrev; + Base.State state; + if (_optimum[cur].Prev1IsChar) + { + posPrev--; + if (_optimum[cur].Prev2) + { + state = _optimum[_optimum[cur].PosPrev2].State; + if (_optimum[cur].BackPrev2 < Base.kNumRepDistances) + state.UpdateRep(); + else + state.UpdateMatch(); + } + else + state = _optimum[posPrev].State; + state.UpdateChar(); + } + else + state = _optimum[posPrev].State; + if (posPrev == cur - 1) + { + if (_optimum[cur].IsShortRep()) + state.UpdateShortRep(); + else + state.UpdateChar(); + } + else + { + UInt32 pos; + if (_optimum[cur].Prev1IsChar && _optimum[cur].Prev2) + { + posPrev = _optimum[cur].PosPrev2; + pos = _optimum[cur].BackPrev2; + state.UpdateRep(); + } + else + { + pos = _optimum[cur].BackPrev; + if (pos < Base.kNumRepDistances) + state.UpdateRep(); + else + state.UpdateMatch(); + } + Optimal opt = _optimum[posPrev]; + if (pos < Base.kNumRepDistances) + { + if (pos == 0) + { + reps[0] = opt.Backs0; + reps[1] = opt.Backs1; + reps[2] = opt.Backs2; + reps[3] = opt.Backs3; + } + else if (pos == 1) + { + reps[0] = opt.Backs1; + reps[1] = opt.Backs0; + reps[2] = opt.Backs2; + reps[3] = opt.Backs3; + } + else if (pos == 2) + { + reps[0] = opt.Backs2; + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs3; + } + else + { + reps[0] = opt.Backs3; + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs2; + } + } + else + { + reps[0] = (pos - Base.kNumRepDistances); + reps[1] = opt.Backs0; + reps[2] = opt.Backs1; + reps[3] = opt.Backs2; + } + } + _optimum[cur].State = state; + _optimum[cur].Backs0 = reps[0]; + _optimum[cur].Backs1 = reps[1]; + _optimum[cur].Backs2 = reps[2]; + _optimum[cur].Backs3 = reps[3]; + UInt32 curPrice = _optimum[cur].Price; + + currentByte = _matchFinder.GetIndexByte(0 - 1); + matchByte = _matchFinder.GetIndexByte((Int32)(0 - reps[0] - 1 - 1)); + + posState = (position & _posStateMask); + + UInt32 curAnd1Price = curPrice + + _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice0() + + _literalEncoder.GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2)). + GetPrice(!state.IsCharState(), matchByte, currentByte); + + Optimal nextOptimum = _optimum[cur + 1]; + + bool nextIsChar = false; + if (curAnd1Price < nextOptimum.Price) + { + nextOptimum.Price = curAnd1Price; + nextOptimum.PosPrev = cur; + nextOptimum.MakeAsChar(); + nextIsChar = true; + } + + matchPrice = curPrice + _isMatch[(state.Index << Base.kNumPosStatesBitsMax) + posState].GetPrice1(); + repMatchPrice = matchPrice + _isRep[state.Index].GetPrice1(); + + if (matchByte == currentByte && + !(nextOptimum.PosPrev < cur && nextOptimum.BackPrev == 0)) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState); + if (shortRepPrice <= nextOptimum.Price) + { + nextOptimum.Price = shortRepPrice; + nextOptimum.PosPrev = cur; + nextOptimum.MakeAsShortRep(); + nextIsChar = true; + } + } + + UInt32 numAvailableBytesFull = _matchFinder.GetNumAvailableBytes() + 1; + numAvailableBytesFull = Math.Min(kNumOpts - 1 - cur, numAvailableBytesFull); + numAvailableBytes = numAvailableBytesFull; + + if (numAvailableBytes < 2) + continue; + if (numAvailableBytes > _numFastBytes) + numAvailableBytes = _numFastBytes; + if (!nextIsChar && matchByte != currentByte) + { + // try Literal + rep0 + UInt32 t = Math.Min(numAvailableBytesFull - 1, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen(0, reps[0], t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateChar(); + UInt32 posStateNext = (position + 1) & _posStateMask; + UInt32 nextRepMatchPrice = curAnd1Price + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1() + + _isRep[state2.Index].GetPrice1(); + { + UInt32 offset = cur + 1 + lenTest2; + while (lenEnd < offset) + _optimum[++lenEnd].Price = kIfinityPrice; + UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice( + 0, lenTest2, state2, posStateNext); + Optimal optimum = _optimum[offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = false; + } + } + } + } + + UInt32 startLen = 2; // speed optimization + + for (UInt32 repIndex = 0; repIndex < Base.kNumRepDistances; repIndex++) + { + UInt32 lenTest = _matchFinder.GetMatchLen(0 - 1, reps[repIndex], numAvailableBytes); + if (lenTest < 2) + continue; + UInt32 lenTestTemp = lenTest; + do + { + while (lenEnd < cur + lenTest) + _optimum[++lenEnd].Price = kIfinityPrice; + UInt32 curAndLenPrice = repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState); + Optimal optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur; + optimum.BackPrev = repIndex; + optimum.Prev1IsChar = false; + } + } + while(--lenTest >= 2); + lenTest = lenTestTemp; + + if (repIndex == 0) + startLen = lenTest + 1; + + // if (_maxMode) + if (lenTest < numAvailableBytesFull) + { + UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, reps[repIndex], t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateRep(); + UInt32 posStateNext = (position + lenTest) & _posStateMask; + UInt32 curAndLenCharPrice = + repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState) + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + + _literalEncoder.GetSubCoder(position + lenTest, + _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)).GetPrice(true, + _matchFinder.GetIndexByte((Int32)((Int32)lenTest - 1 - (Int32)(reps[repIndex] + 1))), + _matchFinder.GetIndexByte((Int32)lenTest - 1)); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); + UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); + + // for(; lenTest2 >= 2; lenTest2--) + { + UInt32 offset = lenTest + 1 + lenTest2; + while(lenEnd < cur + offset) + _optimum[++lenEnd].Price = kIfinityPrice; + UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + Optimal optimum = _optimum[cur + offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + lenTest + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = true; + optimum.PosPrev2 = cur; + optimum.BackPrev2 = repIndex; + } + } + } + } + } + + if (newLen > numAvailableBytes) + { + newLen = numAvailableBytes; + for (numDistancePairs = 0; newLen > _matchDistances[numDistancePairs]; numDistancePairs += 2) ; + _matchDistances[numDistancePairs] = newLen; + numDistancePairs += 2; + } + if (newLen >= startLen) + { + normalMatchPrice = matchPrice + _isRep[state.Index].GetPrice0(); + while (lenEnd < cur + newLen) + _optimum[++lenEnd].Price = kIfinityPrice; + + UInt32 offs = 0; + while (startLen > _matchDistances[offs]) + offs += 2; + + for (UInt32 lenTest = startLen; ; lenTest++) + { + UInt32 curBack = _matchDistances[offs + 1]; + UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState); + Optimal optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur; + optimum.BackPrev = curBack + Base.kNumRepDistances; + optimum.Prev1IsChar = false; + } + + if (lenTest == _matchDistances[offs]) + { + if (lenTest < numAvailableBytesFull) + { + UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, curBack, t); + if (lenTest2 >= 2) + { + Base.State state2 = state; + state2.UpdateMatch(); + UInt32 posStateNext = (position + lenTest) & _posStateMask; + UInt32 curAndLenCharPrice = curAndLenPrice + + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice0() + + _literalEncoder.GetSubCoder(position + lenTest, + _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)). + GetPrice(true, + _matchFinder.GetIndexByte((Int32)lenTest - (Int32)(curBack + 1) - 1), + _matchFinder.GetIndexByte((Int32)lenTest - 1)); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + UInt32 nextMatchPrice = curAndLenCharPrice + _isMatch[(state2.Index << Base.kNumPosStatesBitsMax) + posStateNext].GetPrice1(); + UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2.Index].GetPrice1(); + + UInt32 offset = lenTest + 1 + lenTest2; + while (lenEnd < cur + offset) + _optimum[++lenEnd].Price = kIfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + optimum = _optimum[cur + offset]; + if (curAndLenPrice < optimum.Price) + { + optimum.Price = curAndLenPrice; + optimum.PosPrev = cur + lenTest + 1; + optimum.BackPrev = 0; + optimum.Prev1IsChar = true; + optimum.Prev2 = true; + optimum.PosPrev2 = cur; + optimum.BackPrev2 = curBack + Base.kNumRepDistances; + } + } + } + offs += 2; + if (offs == numDistancePairs) + break; + } + } + } + } + } + + bool ChangePair(UInt32 smallDist, UInt32 bigDist) + { + const int kDif = 7; + return (smallDist < ((UInt32)(1) << (32 - kDif)) && bigDist >= (smallDist << kDif)); + } + + void WriteEndMarker(UInt32 posState) + { + if (!_writeEndMark) + return; + + _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 1); + _isRep[_state.Index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + UInt32 len = Base.kMatchMinLen; + _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + UInt32 posSlot = (1 << Base.kNumPosSlotBits) - 1; + UInt32 lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + int footerBits = 30; + UInt32 posReduced = (((UInt32)1) << footerBits) - 1; + _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); + _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); + } + + void Flush(UInt32 nowPos) + { + ReleaseMFStream(); + WriteEndMarker(nowPos & _posStateMask); + _rangeEncoder.FlushData(); + _rangeEncoder.FlushStream(); + } + + public void CodeOneBlock(out Int64 inSize, out Int64 outSize, out bool finished) + { + inSize = 0; + outSize = 0; + finished = true; + + if (_inStream != null) + { + _matchFinder.SetStream(_inStream); + _matchFinder.Init(); + _needReleaseMFStream = true; + _inStream = null; + if (_trainSize > 0) + _matchFinder.Skip(_trainSize); + } + + if (_finished) + return; + _finished = true; + + + Int64 progressPosValuePrev = nowPos64; + if (nowPos64 == 0) + { + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + UInt32 len, numDistancePairs; // it's not used + ReadMatchDistances(out len, out numDistancePairs); + UInt32 posState = (UInt32)(nowPos64) & _posStateMask; + _isMatch[(_state.Index << Base.kNumPosStatesBitsMax) + posState].Encode(_rangeEncoder, 0); + _state.UpdateChar(); + Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); + _literalEncoder.GetSubCoder((UInt32)(nowPos64), _previousByte).Encode(_rangeEncoder, curByte); + _previousByte = curByte; + _additionalOffset--; + nowPos64++; + } + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + while (true) + { + UInt32 pos; + UInt32 len = GetOptimum((UInt32)nowPos64, out pos); + + UInt32 posState = ((UInt32)nowPos64) & _posStateMask; + UInt32 complexState = (_state.Index << Base.kNumPosStatesBitsMax) + posState; + if (len == 1 && pos == 0xFFFFFFFF) + { + _isMatch[complexState].Encode(_rangeEncoder, 0); + Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); + LiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((UInt32)nowPos64, _previousByte); + if (!_state.IsCharState()) + { + Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - _additionalOffset)); + subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte); + } + else + subCoder.Encode(_rangeEncoder, curByte); + _previousByte = curByte; + _state.UpdateChar(); + } + else + { + _isMatch[complexState].Encode(_rangeEncoder, 1); + if (pos < Base.kNumRepDistances) + { + _isRep[_state.Index].Encode(_rangeEncoder, 1); + if (pos == 0) + { + _isRepG0[_state.Index].Encode(_rangeEncoder, 0); + if (len == 1) + _isRep0Long[complexState].Encode(_rangeEncoder, 0); + else + _isRep0Long[complexState].Encode(_rangeEncoder, 1); + } + else + { + _isRepG0[_state.Index].Encode(_rangeEncoder, 1); + if (pos == 1) + _isRepG1[_state.Index].Encode(_rangeEncoder, 0); + else + { + _isRepG1[_state.Index].Encode(_rangeEncoder, 1); + _isRepG2[_state.Index].Encode(_rangeEncoder, pos - 2); + } + } + if (len == 1) + _state.UpdateShortRep(); + else + { + _repMatchLenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + _state.UpdateRep(); + } + UInt32 distance = _repDistances[pos]; + if (pos != 0) + { + for (UInt32 i = pos; i >= 1; i--) + _repDistances[i] = _repDistances[i - 1]; + _repDistances[0] = distance; + } + } + else + { + _isRep[_state.Index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + _lenEncoder.Encode(_rangeEncoder, len - Base.kMatchMinLen, posState); + pos -= Base.kNumRepDistances; + UInt32 posSlot = GetPosSlot(pos); + UInt32 lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + + if (posSlot >= Base.kStartPosModelIndex) + { + int footerBits = (int)((posSlot >> 1) - 1); + UInt32 baseVal = ((2 | (posSlot & 1)) << footerBits); + UInt32 posReduced = pos - baseVal; + + if (posSlot < Base.kEndPosModelIndex) + RangeCoder.BitTreeEncoder.ReverseEncode(_posEncoders, + baseVal - posSlot - 1, _rangeEncoder, footerBits, posReduced); + else + { + _rangeEncoder.EncodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits); + _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.kAlignMask); + _alignPriceCount++; + } + } + UInt32 distance = pos; + for (UInt32 i = Base.kNumRepDistances - 1; i >= 1; i--) + _repDistances[i] = _repDistances[i - 1]; + _repDistances[0] = distance; + _matchPriceCount++; + } + _previousByte = _matchFinder.GetIndexByte((Int32)(len - 1 - _additionalOffset)); + } + _additionalOffset -= len; + nowPos64 += len; + if (_additionalOffset == 0) + { + // if (!_fastMode) + if (_matchPriceCount >= (1 << 7)) + FillDistancesPrices(); + if (_alignPriceCount >= Base.kAlignTableSize) + FillAlignPrices(); + inSize = nowPos64; + outSize = _rangeEncoder.GetProcessedSizeAdd(); + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((UInt32)nowPos64); + return; + } + + if (nowPos64 - progressPosValuePrev >= (1 << 12)) + { + _finished = false; + finished = false; + return; + } + } + } + } + + void ReleaseMFStream() + { + if (_matchFinder != null && _needReleaseMFStream) + { + _matchFinder.ReleaseStream(); + _needReleaseMFStream = false; + } + } + + void SetOutStream(System.IO.Stream outStream) { _rangeEncoder.SetStream(outStream); } + void ReleaseOutStream() { _rangeEncoder.ReleaseStream(); } + + void ReleaseStreams() + { + ReleaseMFStream(); + ReleaseOutStream(); + } + + void SetStreams(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize) + { + _inStream = inStream; + _finished = false; + Create(); + SetOutStream(outStream); + Init(); + + // if (!_fastMode) + { + FillDistancesPrices(); + FillAlignPrices(); + } + + _lenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); + _lenEncoder.UpdateTables((UInt32)1 << _posStateBits); + _repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.kMatchMinLen); + _repMatchLenEncoder.UpdateTables((UInt32)1 << _posStateBits); + + nowPos64 = 0; + } + + + public void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress) + { + _needReleaseMFStream = false; + try + { + SetStreams(inStream, outStream, inSize, outSize); + while (true) + { + Int64 processedInSize; + Int64 processedOutSize; + bool finished; + CodeOneBlock(out processedInSize, out processedOutSize, out finished); + if (finished) + return; + if (progress != null) + { + progress.SetProgress(processedInSize, processedOutSize); + } + } + } + finally + { + ReleaseStreams(); + } + } + + const int kPropSize = 5; + Byte[] properties = new Byte[kPropSize]; + + public void WriteCoderProperties(System.IO.Stream outStream) + { + properties[0] = (Byte)((_posStateBits * 5 + _numLiteralPosStateBits) * 9 + _numLiteralContextBits); + for (int i = 0; i < 4; i++) + properties[1 + i] = (Byte)((_dictionarySize >> (8 * i)) & 0xFF); + outStream.Write(properties, 0, kPropSize); + } + + UInt32[] tempPrices = new UInt32[Base.kNumFullDistances]; + UInt32 _matchPriceCount; + + void FillDistancesPrices() + { + for (UInt32 i = Base.kStartPosModelIndex; i < Base.kNumFullDistances; i++) + { + UInt32 posSlot = GetPosSlot(i); + int footerBits = (int)((posSlot >> 1) - 1); + UInt32 baseVal = ((2 | (posSlot & 1)) << footerBits); + tempPrices[i] = BitTreeEncoder.ReverseGetPrice(_posEncoders, + baseVal - posSlot - 1, footerBits, i - baseVal); + } + + for (UInt32 lenToPosState = 0; lenToPosState < Base.kNumLenToPosStates; lenToPosState++) + { + UInt32 posSlot; + RangeCoder.BitTreeEncoder encoder = _posSlotEncoder[lenToPosState]; + + UInt32 st = (lenToPosState << Base.kNumPosSlotBits); + for (posSlot = 0; posSlot < _distTableSize; posSlot++) + _posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot); + for (posSlot = Base.kEndPosModelIndex; posSlot < _distTableSize; posSlot++) + _posSlotPrices[st + posSlot] += ((((posSlot >> 1) - 1) - Base.kNumAlignBits) << RangeCoder.BitEncoder.kNumBitPriceShiftBits); + + UInt32 st2 = lenToPosState * Base.kNumFullDistances; + UInt32 i; + for (i = 0; i < Base.kStartPosModelIndex; i++) + _distancesPrices[st2 + i] = _posSlotPrices[st + i]; + for (; i < Base.kNumFullDistances; i++) + _distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + tempPrices[i]; + } + _matchPriceCount = 0; + } + + void FillAlignPrices() + { + for (UInt32 i = 0; i < Base.kAlignTableSize; i++) + _alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i); + _alignPriceCount = 0; + } + + + static string[] kMatchFinderIDs = + { + "BT2", + "BT4", + }; + + static int FindMatchFinder(string s) + { + for (int m = 0; m < kMatchFinderIDs.Length; m++) + if (s == kMatchFinderIDs[m]) + return m; + return -1; + } + + public void SetCoderProperties(CoderPropID[] propIDs, object[] properties) + { + for (UInt32 i = 0; i < properties.Length; i++) + { + object prop = properties[i]; + switch (propIDs[i]) + { + case CoderPropID.NumFastBytes: + { + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 numFastBytes = (Int32)prop; + if (numFastBytes < 5 || numFastBytes > Base.kMatchMaxLen) + throw new InvalidParamException(); + _numFastBytes = (UInt32)numFastBytes; + break; + } + case CoderPropID.Algorithm: + { + /* + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 maximize = (Int32)prop; + _fastMode = (maximize == 0); + _maxMode = (maximize >= 2); + */ + break; + } + case CoderPropID.MatchFinder: + { + if (!(prop is String)) + throw new InvalidParamException(); + EMatchFinderType matchFinderIndexPrev = _matchFinderType; + int m = FindMatchFinder(((string)prop).ToUpper()); + if (m < 0) + throw new InvalidParamException(); + _matchFinderType = (EMatchFinderType)m; + if (_matchFinder != null && matchFinderIndexPrev != _matchFinderType) + { + _dictionarySizePrev = 0xFFFFFFFF; + _matchFinder = null; + } + break; + } + case CoderPropID.DictionarySize: + { + const int kDicLogSizeMaxCompress = 30; + if (!(prop is Int32)) + throw new InvalidParamException(); ; + Int32 dictionarySize = (Int32)prop; + if (dictionarySize < (UInt32)(1 << Base.kDicLogSizeMin) || + dictionarySize > (UInt32)(1 << kDicLogSizeMaxCompress)) + throw new InvalidParamException(); + _dictionarySize = (UInt32)dictionarySize; + int dicLogSize; + for (dicLogSize = 0; dicLogSize < (UInt32)kDicLogSizeMaxCompress; dicLogSize++) + if (dictionarySize <= ((UInt32)(1) << dicLogSize)) + break; + _distTableSize = (UInt32)dicLogSize * 2; + break; + } + case CoderPropID.PosStateBits: + { + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 v = (Int32)prop; + if (v < 0 || v > (UInt32)Base.kNumPosStatesBitsEncodingMax) + throw new InvalidParamException(); + _posStateBits = (int)v; + _posStateMask = (((UInt32)1) << (int)_posStateBits) - 1; + break; + } + case CoderPropID.LitPosBits: + { + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 v = (Int32)prop; + if (v < 0 || v > (UInt32)Base.kNumLitPosStatesBitsEncodingMax) + throw new InvalidParamException(); + _numLiteralPosStateBits = (int)v; + break; + } + case CoderPropID.LitContextBits: + { + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 v = (Int32)prop; + if (v < 0 || v > (UInt32)Base.kNumLitContextBitsMax) + throw new InvalidParamException(); ; + _numLiteralContextBits = (int)v; + break; + } + case CoderPropID.EndMarker: + { + if (!(prop is Boolean)) + throw new InvalidParamException(); + SetWriteEndMarkerMode((Boolean)prop); + break; + } + default: + throw new InvalidParamException(); + } + } + } + + uint _trainSize = 0; + public void SetTrainSize(uint trainSize) + { + _trainSize = trainSize; + } + + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs.meta new file mode 100644 index 00000000..fedcb568 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/LZMA/LzmaEncoder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37dcefc9d952dc34f9fba085a160a968 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder.meta new file mode 100644 index 00000000..cf24bb3c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 73c80167cede90a4a8fc4938bab053c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs new file mode 100644 index 00000000..949c6bbe --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs @@ -0,0 +1,234 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + class Encoder + { + public const uint kTopValue = (1 << 24); + + System.IO.Stream Stream; + + public UInt64 Low; + public uint Range; + uint _cacheSize; + byte _cache; + + long StartPosition; + + public void SetStream(System.IO.Stream stream) + { + Stream = stream; + } + + public void ReleaseStream() + { + Stream = null; + } + + public void Init() + { + StartPosition = Stream.Position; + + Low = 0; + Range = 0xFFFFFFFF; + _cacheSize = 1; + _cache = 0; + } + + public void FlushData() + { + for (int i = 0; i < 5; i++) + ShiftLow(); + } + + public void FlushStream() + { + Stream.Flush(); + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Encode(uint start, uint size, uint total) + { + Low += start * (Range /= total); + Range *= size; + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public void ShiftLow() + { + if ((uint)Low < (uint)0xFF000000 || (uint)(Low >> 32) == 1) + { + byte temp = _cache; + do + { + Stream.WriteByte((byte)(temp + (Low >> 32))); + temp = 0xFF; + } + while (--_cacheSize != 0); + _cache = (byte)(((uint)Low) >> 24); + } + _cacheSize++; + Low = ((uint)Low) << 8; + } + + public void EncodeDirectBits(uint v, int numTotalBits) + { + for (int i = numTotalBits - 1; i >= 0; i--) + { + Range >>= 1; + if (((v >> i) & 1) == 1) + Low += Range; + if (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + } + + public void EncodeBit(uint size0, int numTotalBits, uint symbol) + { + uint newBound = (Range >> numTotalBits) * size0; + if (symbol == 0) + Range = newBound; + else + { + Low += newBound; + Range -= newBound; + } + while (Range < kTopValue) + { + Range <<= 8; + ShiftLow(); + } + } + + public long GetProcessedSizeAdd() + { + return _cacheSize + + Stream.Position - StartPosition + 4; + // (long)Stream.GetProcessedSize(); + } + } + + class Decoder + { + public const uint kTopValue = (1 << 24); + public uint Range; + public uint Code; + // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); + public System.IO.Stream Stream; + + public void Init(System.IO.Stream stream) + { + // Stream.Init(stream); + Stream = stream; + + Code = 0; + Range = 0xFFFFFFFF; + for (int i = 0; i < 5; i++) + Code = (Code << 8) | (byte)Stream.ReadByte(); + } + + public void ReleaseStream() + { + // Stream.ReleaseStream(); + Stream = null; + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Normalize() + { + while (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public void Normalize2() + { + if (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public uint GetThreshold(uint total) + { + return Code / (Range /= total); + } + + public void Decode(uint start, uint size, uint total) + { + Code -= start * Range; + Range *= size; + Normalize(); + } + + public uint DecodeDirectBits(int numTotalBits) + { + uint range = Range; + uint code = Code; + uint result = 0; + for (int i = numTotalBits; i > 0; i--) + { + range >>= 1; + /* + result <<= 1; + if (code >= range) + { + code -= range; + result |= 1; + } + */ + uint t = (code - range) >> 31; + code -= range & (t - 1); + result = (result << 1) | (1 - t); + + if (range < kTopValue) + { + code = (code << 8) | (byte)Stream.ReadByte(); + range <<= 8; + } + } + Range = range; + Code = code; + return result; + } + + public uint DecodeBit(uint size0, int numTotalBits) + { + uint newBound = (Range >> numTotalBits) * size0; + uint symbol; + if (Code < newBound) + { + symbol = 0; + Range = newBound; + } + else + { + symbol = 1; + Code -= newBound; + Range -= newBound; + } + Normalize(); + return symbol; + } + + // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs.meta new file mode 100644 index 00000000..fdbc75b8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a855cbfe314340a43b3d71672dcbb058 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs new file mode 100644 index 00000000..4f0346d1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs @@ -0,0 +1,117 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + struct BitEncoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = (1 << kNumBitModelTotalBits); + const int kNumMoveBits = 5; + const int kNumMoveReducingBits = 2; + public const int kNumBitPriceShiftBits = 6; + + uint Prob; + + public void Init() { Prob = kBitModelTotal >> 1; } + + public void UpdateModel(uint symbol) + { + if (symbol == 0) + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + else + Prob -= (Prob) >> kNumMoveBits; + } + + public void Encode(Encoder encoder, uint symbol) + { + // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol); + // UpdateModel(symbol); + uint newBound = (encoder.Range >> kNumBitModelTotalBits) * Prob; + if (symbol == 0) + { + encoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + } + else + { + encoder.Low += newBound; + encoder.Range -= newBound; + Prob -= (Prob) >> kNumMoveBits; + } + if (encoder.Range < Encoder.kTopValue) + { + encoder.Range <<= 8; + encoder.ShiftLow(); + } + } + + private static UInt32[] ProbPrices = new UInt32[kBitModelTotal >> kNumMoveReducingBits]; + + static BitEncoder() + { + const int kNumBits = (kNumBitModelTotalBits - kNumMoveReducingBits); + for (int i = kNumBits - 1; i >= 0; i--) + { + UInt32 start = (UInt32)1 << (kNumBits - i - 1); + UInt32 end = (UInt32)1 << (kNumBits - i); + for (UInt32 j = start; j < end; j++) + ProbPrices[j] = ((UInt32)i << kNumBitPriceShiftBits) + + (((end - j) << kNumBitPriceShiftBits) >> (kNumBits - i - 1)); + } + } + + public uint GetPrice(uint symbol) + { + return ProbPrices[(((Prob - symbol) ^ ((-(int)symbol))) & (kBitModelTotal - 1)) >> kNumMoveReducingBits]; + } + public uint GetPrice0() { return ProbPrices[Prob >> kNumMoveReducingBits]; } + public uint GetPrice1() { return ProbPrices[(kBitModelTotal - Prob) >> kNumMoveReducingBits]; } + } + + struct BitDecoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = (1 << kNumBitModelTotalBits); + const int kNumMoveBits = 5; + + uint Prob; + + public void UpdateModel(int numMoveBits, uint symbol) + { + if (symbol == 0) + Prob += (kBitModelTotal - Prob) >> numMoveBits; + else + Prob -= (Prob) >> numMoveBits; + } + + public void Init() { Prob = kBitModelTotal >> 1; } + + public uint Decode(RangeCoder.Decoder rangeDecoder) + { + uint newBound = (uint)(rangeDecoder.Range >> kNumBitModelTotalBits) * (uint)Prob; + if (rangeDecoder.Code < newBound) + { + rangeDecoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 0; + } + else + { + rangeDecoder.Range -= newBound; + rangeDecoder.Code -= newBound; + Prob -= (Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 1; + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs.meta new file mode 100644 index 00000000..2a0f148a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3fd3305ff6e02448a7b0470cfe65b77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs new file mode 100644 index 00000000..4b4506f9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs @@ -0,0 +1,157 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + struct BitTreeEncoder + { + BitEncoder[] Models; + int NumBitLevels; + + public BitTreeEncoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitEncoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + Models[i].Init(); + } + + public void Encode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; ) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + } + } + + public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) + { + UInt32 m = 1; + for (UInt32 i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + + public UInt32 GetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; ) + { + bitIndex--; + UInt32 bit = (symbol >> bitIndex) & 1; + price += Models[m].GetPrice(bit); + m = (m << 1) + bit; + } + return price; + } + + public UInt32 ReverseGetPrice(UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex, + int NumBitLevels, UInt32 symbol) + { + UInt32 price = 0; + UInt32 m = 1; + for (int i = NumBitLevels; i > 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += Models[startIndex + m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex, + Encoder rangeEncoder, int NumBitLevels, UInt32 symbol) + { + UInt32 m = 1; + for (int i = 0; i < NumBitLevels; i++) + { + UInt32 bit = symbol & 1; + Models[startIndex + m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + } + + struct BitTreeDecoder + { + BitDecoder[] Models; + int NumBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitDecoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + Models[i].Init(); + } + + public uint Decode(RangeCoder.Decoder rangeDecoder) + { + uint m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) + m = (m << 1) + Models[m].Decode(rangeDecoder); + return m - ((uint)1 << NumBitLevels); + } + + public uint ReverseDecode(RangeCoder.Decoder rangeDecoder) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + + public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, + RangeCoder.Decoder rangeDecoder, int NumBitLevels) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[startIndex + m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs.meta new file mode 100644 index 00000000..df4ba122 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/Compress/RangeCoder/RangeCoderBitTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef79f786b772db344a4792a5cba382ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs new file mode 100644 index 00000000..c8b95c8d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs @@ -0,0 +1,157 @@ +// ICoder.h + +using System; + +namespace SevenZip +{ + ///

+ /// The exception that is thrown when an error in input stream occurs during decoding. + /// + class DataErrorException : ApplicationException + { + public DataErrorException(): base("Data Error") { } + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range. + /// + class InvalidParamException : ApplicationException + { + public InvalidParamException(): base("Invalid Parameter") { } + } + + public interface ICodeProgress + { + /// + /// Callback progress. + /// + /// + /// input size. -1 if unknown. + /// + /// + /// output size. -1 if unknown. + /// + void SetProgress(Int64 inSize, Int64 outSize); + }; + + public interface ICoder + { + /// + /// Codes streams. + /// + /// + /// input Stream. + /// + /// + /// output Stream. + /// + /// + /// input Size. -1 if unknown. + /// + /// + /// output Size. -1 if unknown. + /// + /// + /// callback progress reference. + /// + /// + /// if input stream is not valid + /// + void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress); + }; + + /* + public interface ICoder2 + { + void Code(ISequentialInStream []inStreams, + const UInt64 []inSizes, + ISequentialOutStream []outStreams, + UInt64 []outSizes, + ICodeProgress progress); + }; + */ + + /// + /// Provides the fields that represent properties idenitifiers for compressing. + /// + public enum CoderPropID + { + /// + /// Specifies default property. + /// + DefaultProp = 0, + /// + /// Specifies size of dictionary. + /// + DictionarySize, + /// + /// Specifies size of memory for PPM*. + /// + UsedMemorySize, + /// + /// Specifies order for PPM methods. + /// + Order, + /// + /// Specifies Block Size. + /// + BlockSize, + /// + /// Specifies number of postion state bits for LZMA (0 <= x <= 4). + /// + PosStateBits, + /// + /// Specifies number of literal context bits for LZMA (0 <= x <= 8). + /// + LitContextBits, + /// + /// Specifies number of literal position bits for LZMA (0 <= x <= 4). + /// + LitPosBits, + /// + /// Specifies number of fast bytes for LZ*. + /// + NumFastBytes, + /// + /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". + /// + MatchFinder, + /// + /// Specifies the number of match finder cyckes. + /// + MatchFinderCycles, + /// + /// Specifies number of passes. + /// + NumPasses, + /// + /// Specifies number of algorithm. + /// + Algorithm, + /// + /// Specifies the number of threads. + /// + NumThreads, + /// + /// Specifies mode with end marker. + /// + EndMarker + }; + + + public interface ISetCoderProperties + { + void SetCoderProperties(CoderPropID[] propIDs, object[] properties); + }; + + public interface IWriteCoderProperties + { + void WriteCoderProperties(System.IO.Stream outStream); + } + + public interface ISetDecoderProperties + { + void SetDecoderProperties(byte[] properties); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs.meta new file mode 100644 index 00000000..f4ca52ac --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/7zip/ICoder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f4fba00efa28594ea18888f574d028a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS.meta new file mode 100644 index 00000000..bee3c03d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 59ab7ccacd9a9944bb9456da235d6760 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs new file mode 100644 index 00000000..6f46f5de --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace UnityFS +{ + [Flags] + public enum ArchiveFlags + { + CompressionTypeMask = 0x3f, + BlocksAndDirectoryInfoCombined = 0x40, + BlocksInfoAtTheEnd = 0x80, + OldWebPluginCompatibility = 0x100, + BlockInfoNeedPaddingAtStart = 0x200 + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs.meta new file mode 100644 index 00000000..95b0a254 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ArchiveFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 099de05eebdffaf4baeed3290dc98aaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs new file mode 100644 index 00000000..848d8207 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace UnityFS +{ + public static class BinaryReaderExtensions + { + + public static void AlignStream(this BinaryReader reader, int alignment) + { + var pos = reader.BaseStream.Position; + var mod = pos % alignment; + if (mod != 0) + { + reader.BaseStream.Position += alignment - mod; + } + } + + public static string ReadAlignedString(this BinaryReader reader) + { + var length = reader.ReadInt32(); + if (length > 0 && length <= reader.BaseStream.Length - reader.BaseStream.Position) + { + var stringData = reader.ReadBytes(length); + var result = Encoding.UTF8.GetString(stringData); + reader.AlignStream(4); + return result; + } + return ""; + } + + public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767) + { + var bytes = new List(); + int count = 0; + while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength) + { + var b = reader.ReadByte(); + if (b == 0) + { + break; + } + bytes.Add(b); + count++; + } + return Encoding.UTF8.GetString(bytes.ToArray()); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs.meta new file mode 100644 index 00000000..224d27ef --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryReaderExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a84e427dd05adde44b4345b3fd49007a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs new file mode 100644 index 00000000..8015515b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Text; + +namespace UnityFS +{ + public static class BinaryWriterExtensions + { + public static void AlignStream(this BinaryWriter writer, int alignment) + { + var pos = writer.BaseStream.Position; + var mod = pos % alignment; + if (mod != 0) + { + writer.Write(new byte[alignment - mod]); + } + } + + public static void WriteAlignedString(this BinaryWriter writer, string str) + { + var bytes = Encoding.UTF8.GetBytes(str); + writer.Write(bytes.Length); + writer.Write(bytes); + writer.AlignStream(4); + } + + public static void WriteNullEndString(this BinaryWriter writer, string str) + { + writer.Write(Encoding.UTF8.GetBytes(str)); + writer.Write((byte)0); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs.meta new file mode 100644 index 00000000..053416c6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BinaryWriterExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02eda21769c083346a5bd9b7dca49427 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs new file mode 100644 index 00000000..d2a9eab1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnityFS +{ + public class BundleSubFile + { + public string file; + public byte[] data; + } + + public class BundleFileInfo + { + public string signature; + public uint version; + public string unityVersion; + public string unityRevision; + public ArchiveFlags flags; + public List files; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs.meta new file mode 100644 index 00000000..b33b9b28 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a72550a949e322419b9c5d6e4fe495d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs new file mode 100644 index 00000000..1804cf4f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs @@ -0,0 +1,212 @@ +using LZ4; +using System; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace UnityFS +{ + + public class BundleFileReader + { + + private Header m_Header; + private StorageBlock[] m_BlocksInfo; + private Node[] m_DirectoryInfo; + + private StreamFile[] fileList; + + public BundleFileReader() + { + + } + + public void Load(EndianBinaryReader reader) + { + Debug.Log($"reader. pos:{reader.Position} length:{reader.BaseStream.Length}"); + m_Header = new Header(); + m_Header.signature = reader.ReadStringToNull(); + m_Header.version = reader.ReadUInt32(); + m_Header.unityVersion = reader.ReadStringToNull(); + m_Header.unityRevision = reader.ReadStringToNull(); + System.Diagnostics.Debug.Assert(m_Header.signature == "UnityFS"); + + + m_Header.size = reader.ReadInt64(); + Debug.Log($"header size:{m_Header.size}"); + m_Header.compressedBlocksInfoSize = reader.ReadUInt32(); + m_Header.uncompressedBlocksInfoSize = reader.ReadUInt32(); + m_Header.flags = (ArchiveFlags)reader.ReadUInt32(); + if (m_Header.signature != "UnityFS") + { + reader.ReadByte(); + } + + ReadMetadata(reader); + using (var blocksStream = CreateBlocksStream()) + { + ReadBlocks(reader, blocksStream); + ReadFiles(blocksStream); + } + } + + public BundleFileInfo CreateBundleFileInfo() + { + return new BundleFileInfo + { + signature = m_Header.signature, + version = m_Header.version, + unityVersion = m_Header.unityVersion, + unityRevision = m_Header.unityRevision, + files = fileList.Select(f => new BundleSubFile { file = f.path, data = f.stream.ReadAllBytes() }).ToList(), + }; + } + + private byte[] ReadBlocksInfoAndDirectoryMetadataUnCompressedBytes(EndianBinaryReader reader) + { + byte[] metadataUncompressBytes; + if (m_Header.version >= 7) + { + reader.AlignStream(16); + } + if ((m_Header.flags & ArchiveFlags.BlocksInfoAtTheEnd) != 0) + { + var position = reader.Position; + reader.Position = reader.BaseStream.Length - m_Header.compressedBlocksInfoSize; + metadataUncompressBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize); + reader.Position = position; + } + else //0x40 BlocksAndDirectoryInfoCombined + { + metadataUncompressBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize); + } + return metadataUncompressBytes; + } + + private byte[] DecompressBytes(CompressionType compressionType, byte[] compressedBytes, uint uncompressedSize) + { + switch (compressionType) + { + case CompressionType.None: + { + return compressedBytes; + } + case CompressionType.Lzma: + { + var uncompressedStream = new MemoryStream((int)(uncompressedSize)); + using (var compressedStream = new MemoryStream(compressedBytes)) + { + ComparessHelper.Decompress7Zip(compressedStream, uncompressedStream, m_Header.compressedBlocksInfoSize, m_Header.uncompressedBlocksInfoSize); + } + return uncompressedStream.ReadAllBytes(); + } + case CompressionType.Lz4: + case CompressionType.Lz4HC: + { + var uncompressedBytes = new byte[uncompressedSize]; + var numWrite = LZ4Codec.Decode(compressedBytes, 0, compressedBytes.Length, uncompressedBytes, 0, uncompressedBytes.Length, true); + if (numWrite != uncompressedSize) + { + throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); + } + return uncompressedBytes; + } + default: + throw new IOException($"Unsupported compression type {compressionType}"); + } + } + + private void ReadMetadata(EndianBinaryReader reader) + { + byte[] compressMetadataBytes = ReadBlocksInfoAndDirectoryMetadataUnCompressedBytes(reader); + MemoryStream metadataStream = new MemoryStream(DecompressBytes((CompressionType)(m_Header.flags & ArchiveFlags.CompressionTypeMask), compressMetadataBytes, m_Header.uncompressedBlocksInfoSize)); + using (var blocksInfoReader = new EndianBinaryReader(metadataStream)) + { + var uncompressedDataHash = blocksInfoReader.ReadBytes(16); + var blocksInfoCount = blocksInfoReader.ReadInt32(); + m_BlocksInfo = new StorageBlock[blocksInfoCount]; + for (int i = 0; i < blocksInfoCount; i++) + { + m_BlocksInfo[i] = new StorageBlock + { + uncompressedSize = blocksInfoReader.ReadUInt32(), + compressedSize = blocksInfoReader.ReadUInt32(), + flags = (StorageBlockFlags)blocksInfoReader.ReadUInt16() + }; + } + + var nodesCount = blocksInfoReader.ReadInt32(); + m_DirectoryInfo = new Node[nodesCount]; + for (int i = 0; i < nodesCount; i++) + { + m_DirectoryInfo[i] = new Node + { + offset = blocksInfoReader.ReadInt64(), + size = blocksInfoReader.ReadInt64(), + flags = blocksInfoReader.ReadUInt32(), + path = blocksInfoReader.ReadStringToNull(), + }; + } + } + if (m_Header.flags.HasFlag(ArchiveFlags.BlockInfoNeedPaddingAtStart)) + { + reader.AlignStream(16); + } + } + + + private Stream CreateBlocksStream() + { + Stream blocksStream; + var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize); + if (uncompressedSizeSum >= int.MaxValue) + { + throw new Exception($"too fig file"); + } + else + { + blocksStream = new MemoryStream((int)uncompressedSizeSum); + } + return blocksStream; + } + + public void ReadFiles(Stream blocksStream) + { + fileList = new StreamFile[m_DirectoryInfo.Length]; + for (int i = 0; i < m_DirectoryInfo.Length; i++) + { + var node = m_DirectoryInfo[i]; + var file = new StreamFile(); + fileList[i] = file; + file.path = node.path; + file.fileName = Path.GetFileName(node.path); + if (node.size >= int.MaxValue) + { + throw new Exception($"exceed max file size"); + /*var memoryMappedFile = MemoryMappedFile.CreateNew(null, entryinfo_size); + file.stream = memoryMappedFile.CreateViewStream();*/ + //var extractPath = path + "_unpacked" + Path.DirectorySeparatorChar; + //Directory.CreateDirectory(extractPath); + //file.stream = new FileStream(extractPath + file.fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + } + file.stream = new MemoryStream((int)node.size); + blocksStream.Position = node.offset; + blocksStream.CopyTo(file.stream, node.size); + file.stream.Position = 0; + } + } + + private void ReadBlocks(EndianBinaryReader reader, Stream blocksStream) + { + foreach (var blockInfo in m_BlocksInfo) + { + var compressedSize = (int)blockInfo.compressedSize; + byte[] compressedBlockBytes = reader.ReadBytes(compressedSize); + var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask); + byte[] uncompressedBlockBytes = DecompressBytes(compressionType, compressedBlockBytes, blockInfo.uncompressedSize); + blocksStream.Write(uncompressedBlockBytes, 0, uncompressedBlockBytes.Length); + } + blocksStream.Position = 0; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs.meta new file mode 100644 index 00000000..8b3cfae7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9b938458cc610e4d8a910c4499693cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs new file mode 100644 index 00000000..7e039cb0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs @@ -0,0 +1,112 @@ +using LZ4; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityFS +{ + public class BundleFileWriter + { + private readonly BundleFileInfo _bundle; + + private readonly List _files = new List(); + private readonly List _blocks = new List(); + + private readonly EndianBinaryWriter _blockDirectoryMetadataStream = new EndianBinaryWriter(new MemoryStream()); + private byte[] _blockBytes; + + public BundleFileWriter(BundleFileInfo bundle) + { + _bundle = bundle; + } + + public void Write(EndianBinaryWriter output) + { + InitBlockAndDirectories(); + + output.WriteNullEndString(_bundle.signature); + output.Write(_bundle.version); + output.WriteNullEndString(_bundle.unityVersion); + output.WriteNullEndString(_bundle.unityRevision); + + BuildBlockDirectoryMetadata(); + + + long sizePos = output.Position; + output.Write(0L); + output.Write((uint)_blockDirectoryMetadataStream.Length); + output.Write((uint)_blockDirectoryMetadataStream.Length); + ArchiveFlags flags = ArchiveFlags.BlocksAndDirectoryInfoCombined | (uint)CompressionType.None; + output.Write((uint)flags); + + if (_bundle.version >= 7) + { + output.AlignStream(16); + } + byte[] metadataBytes = _blockDirectoryMetadataStream.BaseStream.ReadAllBytes(); + output.Write(metadataBytes, 0, metadataBytes.Length); + + byte[] dataBytes = _blockBytes; + output.Write(dataBytes, 0, dataBytes.Length); + + output.Position = sizePos; + output.Write(output.Length); + } + + private void InitBlockAndDirectories() + { + var dataStream = new MemoryStream(); + foreach(var file in _bundle.files) + { + byte[] data = file.data; + _files.Add(new Node { path = file.file, flags = 0, offset = dataStream.Length, size = data.LongLength }); + dataStream.Write(data, 0, data.Length); + } + byte[] dataBytes = dataStream.ToArray(); + + var compressedBlockStream = new MemoryStream(dataBytes.Length / 2); + int blockByteSize = 128 * 1024; + long dataSize = dataBytes.Length; + byte[] tempCompressBlock = new byte[blockByteSize * 2]; + for(long i = 0, blockNum = (dataSize + blockByteSize - 1) / blockByteSize; i < blockNum; i++) + { + long curBlockSize = Math.Min(dataSize, blockByteSize); + dataSize -= curBlockSize; + + int compressedSize = LZ4Codec.Encode(dataBytes, (int)(i * blockByteSize), (int)curBlockSize, tempCompressBlock, 0, tempCompressBlock.Length); + compressedBlockStream.Write(tempCompressBlock, 0, compressedSize); + _blocks.Add(new StorageBlock { flags = (StorageBlockFlags)(int)CompressionType.Lz4, compressedSize = (uint)compressedSize, uncompressedSize = (uint)curBlockSize }); + //Debug.Log($"== block[{i}] uncompressedSize:{curBlockSize} compressedSize:{compressedSize} totalblocksize:{compressedBlockStream.Length}"); + } + _blockBytes = compressedBlockStream.ToArray(); + } + + private void BuildBlockDirectoryMetadata() + { + var hash = new byte[16]; + _blockDirectoryMetadataStream.Write(hash, 0, 16); + + _blockDirectoryMetadataStream.Write((uint)_blocks.Count); + foreach(var b in _blocks) + { + _blockDirectoryMetadataStream.Write(b.uncompressedSize); + _blockDirectoryMetadataStream.Write(b.compressedSize); + _blockDirectoryMetadataStream.Write((ushort)b.flags); + } + + _blockDirectoryMetadataStream.Write((uint)_files.Count); + foreach(var f in _files) + { + _blockDirectoryMetadataStream.Write(f.offset); + _blockDirectoryMetadataStream.Write(f.size); + _blockDirectoryMetadataStream.Write(f.flags); + _blockDirectoryMetadataStream.WriteNullEndString(f.path); + } + //Debug.Log($"block and directory metadata size:{_blockDirectoryMetadataStream.Length}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs.meta new file mode 100644 index 00000000..98d7466f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/BundleFileWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6705163b267de54a868f5e84f6c7024 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs new file mode 100644 index 00000000..032103b7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs @@ -0,0 +1,11 @@ +namespace UnityFS +{ + public enum CompressionType + { + None, + Lzma, + Lz4, + Lz4HC, + Lzham + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs.meta new file mode 100644 index 00000000..ce8a30ff --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/CompressionType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddcd6644c83d2a94f9668d6e913bd80e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs new file mode 100644 index 00000000..d6716a8f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs @@ -0,0 +1,107 @@ +using System; +using System.IO; + +namespace UnityFS +{ + public class EndianBinaryReader : BinaryReader + { + private readonly byte[] buffer; + + public EndianType Endian; + + public EndianBinaryReader(Stream stream, EndianType endian = EndianType.BigEndian) : base(stream) + { + Endian = endian; + buffer = new byte[8]; + } + + public long Position + { + get => BaseStream.Position; + set => BaseStream.Position = value; + } + + private unsafe void ReadBufferBigEndian(byte* dst, byte[] src, int size) + { + System.Diagnostics.Debug.Assert(BitConverter.IsLittleEndian); + for (int i = 0; i < size; i++) + { + dst[i] = src[size - i - 1]; + } + } + + public override short ReadInt16() + { + return (short)ReadUInt16(); + } + + public unsafe override ushort ReadUInt16() + { + if (Endian == EndianType.BigEndian) + { + Read(buffer, 0, 2); + ushort x = 0; + ReadBufferBigEndian((byte*)&x, buffer, 2); + return x; + } + return base.ReadUInt16(); + } + + public override int ReadInt32() + { + return (int)ReadUInt32(); + } + + public unsafe override uint ReadUInt32() + { + if (Endian == EndianType.BigEndian) + { + Read(buffer, 0, 4); + uint x = 0; + ReadBufferBigEndian((byte*)&x, buffer, 4); + return x; + } + return base.ReadUInt32(); + } + + public override long ReadInt64() + { + return (long)ReadUInt64(); + } + + public unsafe override ulong ReadUInt64() + { + if (Endian == EndianType.BigEndian) + { + Read(buffer, 0, 8); + + ulong x = 0; + ReadBufferBigEndian((byte*)&x, buffer, 8); + return x; + } + return base.ReadUInt64(); + } + + public override float ReadSingle() + { + if (Endian == EndianType.BigEndian) + { + Read(buffer, 0, 4); + Array.Reverse(buffer, 0, 4); + return BitConverter.ToSingle(buffer, 0); + } + return base.ReadSingle(); + } + + public override double ReadDouble() + { + if (Endian == EndianType.BigEndian) + { + Read(buffer, 0, 8); + Array.Reverse(buffer); + return BitConverter.ToDouble(buffer, 0); + } + return base.ReadDouble(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs.meta new file mode 100644 index 00000000..53183c03 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b02c037f0fa1014da65773804248d8d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs new file mode 100644 index 00000000..03938a9f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs @@ -0,0 +1,107 @@ +using System; +using System.IO; + +namespace UnityFS +{ + public class EndianBinaryWriter : BinaryWriter + { + private readonly byte[] buffer; + + public EndianType Endian; + + public EndianBinaryWriter(Stream stream, EndianType endian = EndianType.BigEndian) : base(stream) + { + Endian = endian; + buffer = new byte[8]; + } + + public long Position + { + get => BaseStream.Position; + set => BaseStream.Position = value; + } + + public long Length => BaseStream.Length; + + public override void Write(short x) + { + Write((ushort)x); + } + + private unsafe void WriteBufferBigEndian(byte[] dst, byte* src, int size) + { + System.Diagnostics.Debug.Assert(BitConverter.IsLittleEndian); + for(int i = 0; i < size; i++) + { + dst[i] = src[size - i - 1]; + } + } + + public unsafe override void Write(ushort x) + { + if (Endian == EndianType.BigEndian) + { + WriteBufferBigEndian(buffer, (byte*)&x, 2); + Write(buffer, 0, 2); + return; + } + base.Write(x); + } + + public override void Write(int x) + { + Write((uint)x); + } + + public unsafe override void Write(uint x) + { + if (Endian == EndianType.BigEndian) + { + WriteBufferBigEndian(buffer, (byte*)&x, 4); + Write(buffer, 0, 4); + return; + } + base.Write(x); + } + + public override void Write(long x) + { + Write((ulong)x); + } + + public unsafe override void Write(ulong x) + { + if (Endian == EndianType.BigEndian) + { + WriteBufferBigEndian(buffer, (byte*)&x, 8); + Write(buffer, 0, 8); + return; + } + base.Write(x); + } + + public override void Write(float x) + { + if (Endian == EndianType.BigEndian) + { + var buf = BitConverter.GetBytes(x); + Array.Reverse(buf, 0, 4); + Write(buf, 0, 4); + return; + } + base.Write(x); + } + + public override void Write(double x) + { + if (Endian == EndianType.BigEndian) + { + var buf = BitConverter.GetBytes(x); + Array.Reverse(buf, 0, 8); + Write(buf, 0, 8); + return; + } + base.Write(x); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs.meta new file mode 100644 index 00000000..cff85034 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianBinaryWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4107364c7434b2042ad647b28e322513 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs new file mode 100644 index 00000000..53e740fa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnityFS +{ + public enum EndianType + { + LittleEndian, + BigEndian + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs.meta new file mode 100644 index 00000000..c878edc3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/EndianType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f6cbab4506c18248b410a164be891d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs new file mode 100644 index 00000000..193b1f28 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityFS; + +namespace HybridCLR.Editor.UnityBinFileReader +{ + public class Dataunity3dPatcher + { + + public void ApplyPatch(string dataunity3dFile, List hotUpdateAssemblies) + { + var reader = new BundleFileReader(); + using (var fs = new EndianBinaryReader(new MemoryStream(File.ReadAllBytes(dataunity3dFile)))) + { + reader.Load(fs); + } + + var info = reader.CreateBundleFileInfo(); + //Debug.Log($"name:{info.signature} version:{info.version} files:{info.files.Count}"); + //foreach (var file in info.files) + //{ + // Debug.Log($"file:{file.file} size:{file.data.Length}"); + //} + + var globalgamemanagersFile = info.files.Find(f => f.file == "globalgamemanagers"); + //Debug.LogFormat("gobalgamemanagers origin size:{0}", globalgamemanagersFile.data.Length); + + var ggdBinFile = new UnityBinFile(); + ggdBinFile.LoadFromStream(new MemoryStream(globalgamemanagersFile.data)); + ggdBinFile.AddScriptingAssemblies(hotUpdateAssemblies); + byte[] patchedGlobalgamedatasBytes = ggdBinFile.CreatePatchedBytes(); + //Debug.LogFormat("gobalgamemanagers post patche size:{0}", patchedGlobalgamedatasBytes.Length); + globalgamemanagersFile.data = patchedGlobalgamedatasBytes; + + var writer = new BundleFileWriter(info); + var output = new MemoryStream(); + writer.Write(new EndianBinaryWriter(output)); + Debug.Log($"patch file:{dataunity3dFile} size:{output.Length}"); + + //string bakFile = dataunity3dFile + ".bak"; + //if (!File.Exists(bakFile)) + //{ + // File.Copy(dataunity3dFile, bakFile); + //} + File.WriteAllBytes(dataunity3dFile, output.ToArray()); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs.meta new file mode 100644 index 00000000..b33d83c5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/GlobalgamedatasPatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 653a22d285c79f44a8113c5571b2d26b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs new file mode 100644 index 00000000..2715ccea --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs @@ -0,0 +1,14 @@ +namespace UnityFS +{ + public class Header + { + public string signature; + public uint version; + public string unityVersion; + public string unityRevision; + public long size; + public uint compressedBlocksInfoSize; + public uint uncompressedBlocksInfoSize; + public ArchiveFlags flags; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs.meta new file mode 100644 index 00000000..e5a516a1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Header.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f121e0520fa65c240884d43fd00b3c2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs new file mode 100644 index 00000000..d9cfd97f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs @@ -0,0 +1,10 @@ +namespace UnityFS +{ + public class Node + { + public long offset; + public long size; + public uint flags; + public string path; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs.meta new file mode 100644 index 00000000..0736374c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/Node.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3eea8a6a32b6ac4ba609b39715e25e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs new file mode 100644 index 00000000..71b9ab34 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityFS +{ + public class ScriptingAssembliesJsonPatcher + { + [Serializable] + private class ScriptingAssemblies + { + public List names; + public List types; + } + + private string _file; + ScriptingAssemblies _scriptingAssemblies; + + public void Load(string file) + { + _file = file; + string content = File.ReadAllText(file); + _scriptingAssemblies = JsonUtility.FromJson(content); + } + + public void AddScriptingAssemblies(List assemblies) + { + foreach (string name in assemblies) + { + if (!_scriptingAssemblies.names.Contains(name)) + { + _scriptingAssemblies.names.Add(name); + _scriptingAssemblies.types.Add(16); // user dll type + Debug.Log($"[PatchScriptAssembliesJson] add hotfix assembly:{name} to {_file}"); + } + } + } + + public void Save(string jsonFile) + { + string content = JsonUtility.ToJson(_scriptingAssemblies); + + File.WriteAllText(jsonFile, content); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs.meta new file mode 100644 index 00000000..b1d4449a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/ScriptingAssembliesJsonPatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f2dd29d56a640d4ebd1c2fd374b7638 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs new file mode 100644 index 00000000..2d17669a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using SevenZip.Compression.LZMA; + + +namespace UnityFS +{ + public static class ComparessHelper + { + public static MemoryStream Decompress7Zip(MemoryStream inStream) + { + var decoder = new Decoder(); + + inStream.Seek(0, SeekOrigin.Begin); + var newOutStream = new MemoryStream(); + + var properties = new byte[5]; + if (inStream.Read(properties, 0, 5) != 5) + throw new Exception("input .lzma is too short"); + long outSize = 0; + for (var i = 0; i < 8; i++) + { + var v = inStream.ReadByte(); + if (v < 0) + throw new Exception("Can't Read 1"); + outSize |= ((long)(byte)v) << (8 * i); + } + decoder.SetDecoderProperties(properties); + + var compressedSize = inStream.Length - inStream.Position; + decoder.Code(inStream, newOutStream, compressedSize, outSize, null); + + newOutStream.Position = 0; + return newOutStream; + } + + public static void Decompress7Zip(Stream compressedStream, Stream decompressedStream, long compressedSize, long decompressedSize) + { + var basePosition = compressedStream.Position; + var decoder = new Decoder(); + var properties = new byte[5]; + if (compressedStream.Read(properties, 0, 5) != 5) + throw new Exception("input .lzma is too short"); + decoder.SetDecoderProperties(properties); + decoder.Code(compressedStream, decompressedStream, compressedSize - 5, decompressedSize, null); + compressedStream.Position = basePosition + compressedSize; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs.meta new file mode 100644 index 00000000..77f375b4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/SevenZipHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6606a654e10b3ba48b76b566b903b353 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs new file mode 100644 index 00000000..148c4d1b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs @@ -0,0 +1,9 @@ +namespace UnityFS +{ + public class StorageBlock + { + public uint compressedSize; + public uint uncompressedSize; + public StorageBlockFlags flags; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs.meta new file mode 100644 index 00000000..e1e8ad9c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40dc58bec5631f14c9c17c8a486496d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs new file mode 100644 index 00000000..619fcacc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace UnityFS +{ + [Flags] + public enum StorageBlockFlags + { + CompressionTypeMask = 0x3f, + Streamed = 0x40 + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs.meta new file mode 100644 index 00000000..d3f199d4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StorageBlockFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79b9ed6799d3caf459cf2dfae5765a23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs new file mode 100644 index 00000000..ecffd075 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs @@ -0,0 +1,32 @@ +using System.IO; + +namespace UnityFS +{ + public static class StreamExtensions + { + private const int BufferSize = 81920; + + public static void CopyTo(this Stream source, Stream destination, long size) + { + var buffer = new byte[BufferSize]; + for (var left = size; left > 0; left -= BufferSize) + { + int toRead = BufferSize < left ? BufferSize : (int)left; + int read = source.Read(buffer, 0, toRead); + destination.Write(buffer, 0, read); + if (read != toRead) + { + return; + } + } + } + + public static byte[] ReadAllBytes(this Stream source) + { + source.Position = 0; + var bytes = new byte[source.Length]; + source.Read(bytes, 0, bytes.Length); + return bytes; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs.meta new file mode 100644 index 00000000..a06440e8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2262fbf5672028a48b0c63821d7ff0c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs new file mode 100644 index 00000000..0fffc060 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace UnityFS +{ + public class StreamFile + { + public string path; + public string fileName; + public Stream stream; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs.meta new file mode 100644 index 00000000..7c14e8dc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/StreamFile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fad7df04825c947489aad0d5d0c191a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs new file mode 100644 index 00000000..ad4ad5fc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs @@ -0,0 +1,124 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using System.Text; +using System.Reflection; +using System; +using System.Linq; + +namespace UnityFS +{ + /// + /// Unity 生成的二进制文件(本代码不支持5.x之前的版本) + /// + public unsafe class UnityBinFile + { + /* + * MonoManager: idx: 6; + * type: metaData.types[objects[6].typeID] + */ + public const int kMonoManagerIdx = 6; + + public FileHeader header; + public MetaData metaData; + public ScriptsData scriptsData; + + private Stream _originStream; + + public void LoadFromStream(Stream source) + { + _originStream = source; + using (var br = new BinaryReader(source, Encoding.UTF8, true)) + { + header.LoadFromStream(br); + // 按理说 metaData 应该新开一个buffer来避免加载时的对齐逻辑问题,但由于 sizeof(Header) = 20,已经对齐到4了,所以可以连续读 + metaData.LoadFromStream(br, header.dataOffset); + scriptsData = metaData.GetScriptData(br); + } + } + + public void Load(string path) + { + LoadFromStream(new MemoryStream(File.ReadAllBytes(path))); + } + + public void AddScriptingAssemblies(List assemblies) + { + foreach (string name in assemblies) + { + if (!scriptsData.dllNames.Contains(name)) + { + scriptsData.dllNames.Add(name); + scriptsData.dllTypes.Add(16); // user dll type + Debug.Log($"[PatchScriptAssembliesJson] add dll:{name} to globalgamemanagers"); + } + } + } + + public byte[] CreatePatchedBytes() + { + var fsR = _originStream; + fsR.Position = 0; + var brR = new BinaryReader(fsR, Encoding.UTF8, true); + + var ms = new MemoryStream((int)(header.fileSize * 1.5f)); + var bw = new BinaryWriter(ms, Encoding.UTF8, true); + + /* + * 开始写入data + * dll名称列表存储于 data 区段,修改其数据并不会影响 MetaData 大小,因此 dataOffset 不会改变 + */ + ms.Position = header.dataOffset; + + Dictionary newObjInfos = new Dictionary(); + foreach (var kv in metaData.objects) + { + long objID = kv.Key; + ObjectInfo objInfo = kv.Value; + + byte[] buff = new byte[objInfo.size]; + fsR.Position = objInfo.realPos; + brR.Read(buff, 0, buff.Length); + + + {// unity 的数据偏移貌似会对齐到 8 + int newPos = (((int)ms.Position + 7) >> 3) << 3; + int gapSize = newPos - (int)ms.Position; + + for (int i = 0; i < gapSize; i++) + bw.Write((byte)0); + + objInfo.dataPos = (uint)ms.Position - header.dataOffset; // 重定位数据偏移 + } + + if (objID != kMonoManagerIdx) + bw.Write(buff, 0, buff.Length); + else + objInfo.size = (uint)scriptsData.SaveToStream(bw); + + newObjInfos.Add(objID, objInfo); + } + + metaData.objects = newObjInfos; + header.fileSize = (uint)ms.Position; + + ms.Position = 0; + header.SaveToStream(bw); + metaData.SaveToStream(bw); + + brR.Close(); + + // 写入新文件 + ms.Position = 0; + return ms.ToArray(); + } + + public void Save(string newPath) + { + byte[] patchedBytes = CreatePatchedBytes(); + File.WriteAllBytes(newPath, patchedBytes); + } + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs.meta new file mode 100644 index 00000000..457dbb76 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f9902041e9a1ff4c9f2d65d6384530d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs new file mode 100644 index 00000000..637dc4ee --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs @@ -0,0 +1,397 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using static UnityFS.UnityBinUtils; + +namespace UnityFS +{ + public struct FileHeader + { + public const int kSize = 20; + + public uint dataSize => fileSize - metadataSize; + + public uint metadataSize; + public uint fileSize; + public uint version; + public uint dataOffset; + public byte endianess; + + public void LoadFromStream(BinaryReader br) + { + long startPos = br.BaseStream.Position; + metadataSize = br.ReadUInt32(); + fileSize = br.ReadUInt32(); + version = br.ReadUInt32(); + dataOffset = br.ReadUInt32(); + endianess = br.ReadByte(); + br.BaseStream.Position = startPos + kSize; + + SwapEndianess(); + } + + public long SaveToStream(BinaryWriter bw) + { + SwapEndianess(); + + long startPos = bw.BaseStream.Position; + bw.Write(metadataSize); + bw.Write(fileSize); + bw.Write(version); + bw.Write(dataOffset); + bw.Write(endianess); + bw.BaseStream.Position = startPos + kSize; + return kSize; + } + + void SwapEndianess() + { + SwapUInt(ref metadataSize); + SwapUInt(ref fileSize); + SwapUInt(ref version); + SwapUInt(ref dataOffset); + } + } + + public struct MetaData + { + public long dataStartPos; + + public string version; + public uint platform; + public bool enableTypeTree; + public int typeCount; + public ObjectType[] types; + public int objectCount; + public Dictionary objects; + public int scriptTypeCount; + public ScriptType[] scriptTypes; + public int externalsCount; + public ExternalInfo[] externals; + +#if UNITY_2019_2_OR_NEWER + public int refTypeCount; + public ObjectType[] refTypes; +#endif + public string dummyStr; + + public void LoadFromStream(BinaryReader br, uint dataOffset) + { + long startPos = br.BaseStream.Position; + dataStartPos = startPos; + + version = br.ReadRawString(); + platform = br.ReadUInt32(); + enableTypeTree = br.ReadBoolean(); + typeCount = br.ReadInt32(); + types = new ObjectType[typeCount]; + + for (int i = 0; i < typeCount; i++) + { + types[i].LoadFromStream(br); + } + + objectCount = br.ReadInt32(); + objects = new Dictionary(); + for(int i = 0; i < objectCount; i++) + { + long id = br.AlignedReadInt64(); + ObjectInfo objInfo = new ObjectInfo(); + objInfo.LoadFromStream(br); + objInfo.realPos = objInfo.dataPos + dataOffset; + + objects.Add(id, objInfo); + } + + scriptTypeCount = br.ReadInt32(); + scriptTypes = new ScriptType[scriptTypeCount]; + for(int i = 0; i < scriptTypeCount; i++) + { + scriptTypes[i].LoadFromStream(br); + } + + externalsCount = br.ReadInt32(); + externals = new ExternalInfo[externalsCount]; + for(int i = 0; i < externalsCount; i++) + { + externals[i].LoadFromStream(br); + } + +#if UNITY_2019_2_OR_NEWER + refTypeCount = br.ReadInt32(); + refTypes = new ObjectType[refTypeCount]; + for(int i = 0; i < refTypeCount; i++) + { + refTypes[i].LoadFromStream(br); + } +#endif + dummyStr = br.ReadRawString(); + } + + public long SaveToStream(BinaryWriter bw) + { + long startPos = bw.BaseStream.Position; + bw.WriteRawString(version); + bw.Write(platform); + bw.Write(enableTypeTree); + + bw.Write(typeCount); + foreach(var type in types) + type.SaveToStream(bw); + + bw.Write(objectCount); + foreach (var kv in objects) + { + bw.AlignedWriteInt64(kv.Key); + kv.Value.SaveToStream(bw); + } + + bw.Write(scriptTypeCount); + foreach(var st in scriptTypes) + st.SaveToStream(bw); + + bw.Write(externalsCount); + foreach(var external in externals) + external.SaveToStream(bw); + +#if UNITY_2019_2_OR_NEWER + bw.Write(refTypeCount); + foreach(var refT in refTypes) + refT.SaveToStream(bw); +#endif + + bw.WriteRawString(dummyStr); + + return bw.BaseStream.Position - startPos; + } + + public ScriptsData GetScriptData(BinaryReader br) + { + ObjectInfo objInfo = objects[UnityBinFile.kMonoManagerIdx]; + br.BaseStream.Seek(objInfo.realPos, SeekOrigin.Begin); + + ScriptsData data = new ScriptsData(); + data.LoadFromStream(br); + return data; + } + } + + public struct ObjectType + { + public int typeID; + public bool isStriped; + public short scriptTypeIndex; + + public bool needReadScriptHash; // dont save + + public Hash scriptSigHash; + public Hash typeHash; + + public void LoadFromStream(BinaryReader br) + { + typeID = br.ReadInt32(); + isStriped = br.ReadBoolean(); + scriptTypeIndex = br.ReadInt16(); + + needReadScriptHash = typeID == -1 || typeID == 0x72; + if(needReadScriptHash) + scriptSigHash.LoadFromStream(br); + + typeHash.LoadFromStream(br); + + // GlobalManagers does not has TypeTrees + } + + public long SaveToStream(BinaryWriter bw) + { + long startPos = bw.BaseStream.Position; + bw.Write(typeID); + bw.Write(isStriped); + bw.Write(scriptTypeIndex); + + if(needReadScriptHash) + scriptSigHash.SaveToStream(bw); + + typeHash.SaveToStream(bw); + return bw.BaseStream.Position - startPos; + } + + public int Size() + { + int ret = 0; + ret += sizeof(int); + ret += sizeof(bool); + ret += sizeof(short); + + if (needReadScriptHash) + ret += Hash.kSize; + + ret += Hash.kSize; + return ret; + } + } + + public struct ObjectInfo + { + public const int kSize = 12; + + public uint dataPos; + public uint size; + public uint typeID; + + public uint realPos; // dataPos + Header.dataOffset; // dont save + + public void LoadFromStream(BinaryReader br) + { + dataPos = br.ReadUInt32(); + size = br.ReadUInt32(); + typeID = br.ReadUInt32(); + } + + public long SaveToStream(BinaryWriter bw) + { + bw.Write(dataPos); + bw.Write(size); + bw.Write(typeID); + return kSize; + } + } + + public struct ScriptType + { + public int localFileIndex; + public long localIdentifierOfBin; + + public void LoadFromStream(BinaryReader br) + { + localFileIndex = br.ReadInt32(); + localIdentifierOfBin = br.AlignedReadInt64(); + } + + public long SaveToStream(BinaryWriter bw) + { + long startPos = bw.BaseStream.Position; + bw.Write(localFileIndex); + bw.AlignedWriteInt64(localIdentifierOfBin); + return bw.BaseStream.Position - startPos; + } + } + + public struct ExternalInfo + { + public string dummy; + public Hash guid; + public int type; + public string name; + + public void LoadFromStream(BinaryReader br) + { + dummy = br.ReadRawString(); + guid.LoadFromStream(br); + type = br.ReadInt32(); + name = br.ReadRawString(); + } + + public long SaveToStream(BinaryWriter bw) + { + long startPos = bw.BaseStream.Position; + bw.WriteRawString(dummy); + guid.SaveToStream(bw); + bw.Write(type); + bw.WriteRawString(name); + return bw.BaseStream.Position - startPos; + } + } + + public struct ScriptsData + { + public ScriptID[] scriptIDs; + public List dllNames; + public List dllTypes; // 16 is user type + + public void LoadFromStream(BinaryReader br) + { + { + int count = br.ReadInt32(); + scriptIDs = new ScriptID[count]; + for(int i = 0; i < count; i++) + scriptIDs[i].LoadFromStream(br); + } + { + int count = br.ReadInt32(); + dllNames = new List(count); + for (var i = 0; i < count; i++) + dllNames.Add(br.ReadSizeString()); + } + { + int count = br.ReadInt32(); + dllTypes = new List(count); + for(var i = 0; i < count; i++) + dllTypes.Add(br.ReadInt32()); + } + } + + public long SaveToStream(BinaryWriter bw) + { + long startPos = bw.BaseStream.Position; + bw.Write(scriptIDs.Length); + for(int i = 0; i < scriptIDs.Length; i++) + scriptIDs[i].SaveToStream(bw); + + bw.Write(dllNames.Count); + for(int i = 0, imax = dllNames.Count; i < imax; i++) + bw.WriteSizeString(dllNames[i]); + + bw.Write(dllTypes.Count); + for(int i = 0, imax = dllTypes.Count; i < imax; i++) + bw.Write(dllTypes[i]); + + return bw.BaseStream.Position - startPos; + } + } + + public struct ScriptID + { + public int fileID; + public long pathID; // localIdentifier + + public void LoadFromStream(BinaryReader br) + { + fileID = br.ReadInt32(); + pathID = br.ReadInt64(); + } + + public long SaveToStream(BinaryWriter bw) + { + bw.Write(fileID); + bw.Write(pathID); + return 4 + 8; + } + } + + public struct Hash + { + public const int kSize = 16; + + public int[] data; + + public void LoadFromStream(BinaryReader br) + { + data = new int[4]; + for(int i = 0; i < data.Length; i++) + { + data[i] = br.ReadInt32(); + } + } + + public long SaveToStream(BinaryWriter bw) + { + for(int i = 0; i < data.Length; i++) + { + bw.Write(data[i]); + } + return kSize; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs.meta new file mode 100644 index 00000000..89a829a6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinFileDefines.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10655ce82e730324db6ae297f77df04b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs new file mode 100644 index 00000000..edeefb30 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs @@ -0,0 +1,78 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using System.Text; + +namespace UnityFS +{ + public static class UnityBinUtils + { + public static void SwapUInt(ref uint val) + { + val = (val >> 24) | ((val >> 8) & 0x0000ff00) | ((val << 8) & 0x00ff0000) | (val << 24); + } + + public static string ReadRawString(this BinaryReader br) + { + long startPos = br.BaseStream.Position; + while (true) + { + byte val = br.ReadByte(); + if(val == 0) + break; + } + int size = (int)(br.BaseStream.Position - startPos); + br.BaseStream.Position = startPos; + + byte[] buffer = br.ReadBytes(size); + string ret = Encoding.UTF8.GetString(buffer, 0, size - 1); + + return ret; + } + + public static void WriteRawString(this BinaryWriter bw, string str) + { + byte[] buffer = Encoding.UTF8.GetBytes(str); + bw.Write(buffer, 0, buffer.Length); + bw.Write((byte)0); + } + + public static string ReadSizeString(this BinaryReader br) + { + int size = br.ReadInt32(); + byte[] buff = br.ReadBytes(size); + br.BaseStream.AlignOffset4(); + + string ret = Encoding.UTF8.GetString(buff); + return ret; + } + + public static void WriteSizeString(this BinaryWriter bw, string str) + { + byte[] buff = Encoding.UTF8.GetBytes(str); + bw.Write(buff.Length); + bw.Write(buff, 0, buff.Length); + bw.BaseStream.AlignOffset4(); + } + + public static void AlignOffset4(this Stream stream) + { + int offset = (((int)stream.Position + 3) >> 2) << 2; + stream.Position = offset; + } + + public static long AlignedReadInt64(this BinaryReader br) + { + br.BaseStream.AlignOffset4(); + return br.ReadInt64(); + } + + public static void AlignedWriteInt64(this BinaryWriter bw, long val) + { + bw.BaseStream.AlignOffset4(); + bw.Write(val); + } + } +} + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs.meta new file mode 100644 index 00000000..4be54e7d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityFS/UnityBinUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12a24c30a3914be418be10cfebfa9649 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta new file mode 100644 index 00000000..4fab614e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 13fe0cab0b357464d889de45c8d98850 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs new file mode 100644 index 00000000..6ec2fa7f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs @@ -0,0 +1,320 @@ +using DotNetDetour; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.Linq; + +namespace MonoHook +{ + public unsafe abstract class CodePatcher + { + public bool isValid { get; protected set; } + + protected void* _pTarget, _pReplace, _pProxy; + protected int _jmpCodeSize; + protected byte[] _targetHeaderBackup; + + public CodePatcher(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize) + { + _pTarget = target.ToPointer(); + _pReplace = replace.ToPointer(); + _pProxy = proxy.ToPointer(); + _jmpCodeSize = jmpCodeSize; + } + + public void ApplyPatch() + { + BackupHeader(); + EnableAddrModifiable(); + PatchTargetMethod(); + PatchProxyMethod(); + FlushICache(); + } + + public void RemovePatch() + { + if (_targetHeaderBackup == null) + return; + + EnableAddrModifiable(); + RestoreHeader(); + FlushICache(); + } + + protected void BackupHeader() + { + if (_targetHeaderBackup != null) + return; + + uint requireSize = LDasm.SizeofMinNumByte(_pTarget, _jmpCodeSize); + _targetHeaderBackup = new byte[requireSize]; + + fixed (void* ptr = _targetHeaderBackup) + HookUtils.MemCpy(ptr, _pTarget, _targetHeaderBackup.Length); + } + + protected void RestoreHeader() + { + if (_targetHeaderBackup == null) + return; + + HookUtils.MemCpy_Jit(_pTarget, _targetHeaderBackup); + } + + protected void PatchTargetMethod() + { + byte[] buff = GenJmpCode(_pTarget, _pReplace); + HookUtils.MemCpy_Jit(_pTarget, buff); + } + protected void PatchProxyMethod() + { + if (_pProxy == null) + return; + + // copy target's code to proxy + HookUtils.MemCpy_Jit(_pProxy, _targetHeaderBackup); + + // jmp to target's new position + long jmpFrom = (long)_pProxy + _targetHeaderBackup.Length; + long jmpTo = (long)_pTarget + _targetHeaderBackup.Length; + + byte[] buff = GenJmpCode((void*)jmpFrom, (void*)jmpTo); + HookUtils.MemCpy_Jit((void*)jmpFrom, buff); + } + + protected void FlushICache() + { + HookUtils.FlushICache(_pTarget, _targetHeaderBackup.Length); + HookUtils.FlushICache(_pProxy, _targetHeaderBackup.Length * 2); + } + protected abstract byte[] GenJmpCode(void* jmpFrom, void* jmpTo); + +#if ENABLE_HOOK_DEBUG + protected string PrintAddrs() + { + if (IntPtr.Size == 4) + return $"target:0x{(uint)_pTarget:x}, replace:0x{(uint)_pReplace:x}, proxy:0x{(uint)_pProxy:x}"; + else + return $"target:0x{(ulong)_pTarget:x}, replace:0x{(ulong)_pReplace:x}, proxy:0x{(ulong)_pProxy:x}"; + } +#endif + + private void EnableAddrModifiable() + { + HookUtils.SetAddrFlagsToRWX(new IntPtr(_pTarget), _targetHeaderBackup.Length); + HookUtils.SetAddrFlagsToRWX(new IntPtr(_pProxy), _targetHeaderBackup.Length + _jmpCodeSize); + } + } + + public unsafe class CodePatcher_x86 : CodePatcher + { + protected static readonly byte[] s_jmpCode = new byte[] // 5 bytes + { + 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp $val ; $val = $dst - $src - 5 + }; + + public CodePatcher_x86(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = (int)jmpTo - (int)jmpFrom - 5; + + fixed(void * p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr = 0xE9; + int* pOffset = (int*)(ptr + 1); + *pOffset = val; + } + return ret; + } + } + + /// + /// x64下2G 内的跳转 + /// + public unsafe class CodePatcher_x64_near : CodePatcher_x86 // x64_near pathcer code is same to x86 + { + public CodePatcher_x64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy) { } + } + + /// + /// x64下距离超过2G的跳转 + /// + public unsafe class CodePatcher_x64_far : CodePatcher + { + protected static readonly byte[] s_jmpCode = new byte[] // 12 bytes + { + // 由于 rax 会被函数作为返回值修改,并且不会被做为参数使用,因此修改是安全的 + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, + 0x50, // push rax + 0xC3 // ret + }; + + //protected static readonly byte[] s_jmpCode2 = new byte[] // 14 bytes + //{ + // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF // jmp [rip - 0xe] + //}; + + public CodePatcher_x64_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { } + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = 0x48; + *ptr++ = 0xB8; + *(long*)ptr = (long)jmpTo; + ptr += 8; + *ptr++ = 0x50; + *ptr++ = 0xC3; + } + return ret; + } + } + + public unsafe class CodePatcher_arm32_near : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 4 bytes + { + 0x00, 0x00, 0x00, 0xEA, // B $val ; $val = (($dst - $src) / 4 - 2) & 0x1FFFFFF + }; + + public CodePatcher_arm32_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) >= ((1 << 25) - 1)) + throw new ArgumentException("address offset of target and replace must less than ((1 << 25) - 1)"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm32_near: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = ((int)jmpTo - (int)jmpFrom) / 4 - 2; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = (byte)val; + *ptr++ = (byte)(val >> 8); + *ptr++ = (byte)(val >> 16); + *ptr++ = 0xEA; + } + return ret; + } + } + + public unsafe class CodePatcher_arm32_far : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 8 bytes + { + 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] + 0x00, 0x00, 0x00, 0x00, // $val + }; + + public CodePatcher_arm32_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) < ((1 << 25) - 1)) + throw new ArgumentException("address offset of target and replace must larger than ((1 << 25) - 1), please use InstructionModifier_arm32_near instead"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm32_far: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + + fixed (void* p = &ret[0]) + { + uint* ptr = (uint*)p; + *ptr++ = 0xE51FF004; + *ptr = (uint)jmpTo; + } + return ret; + } + } + + /// + /// arm64 下 ±128MB 范围内的跳转 + /// + public unsafe class CodePatcher_arm64_near : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 4 bytes + { + /* + * from 0x14 to 0x17 is B opcode + * offset bits is 26 + * https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/B--Branch- + */ + 0x00, 0x00, 0x00, 0x14, // B $val ; $val = (($dst - $src)/4) & 7FFFFFF + }; + + public CodePatcher_arm64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) >= ((1 << 26) - 1) * 4) + throw new ArgumentException("address offset of target and replace must less than (1 << 26) - 1) * 4"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm64: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = (int)((long)jmpTo - (long)jmpFrom) / 4; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = (byte)val; + *ptr++ = (byte)(val >> 8); + *ptr++ = (byte)(val >> 16); + + byte last = (byte)(val >> 24); + last &= 0b11; + last |= 0x14; + + *ptr = last; + } + return ret; + } + } + + /// + /// arm64 远距离跳转 + /// + public unsafe class CodePatcher_arm64_far : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 20 bytes(字节数过多,太危险了,不建议使用) + { + /* + * ADR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/ADR--Form-PC-relative-address- + * BR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/BR--Branch-to-Register- + */ + 0x6A, 0x00, 0x00, 0x10, // ADR X10, #C + 0x4A, 0x01, 0x40, 0xF9, // LDR X10, [X10,#0] + 0x40, 0x01, 0x1F, 0xD6, // BR X10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // $dst + }; + + public CodePatcher_arm64_far(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize) : base(target, replace, proxy, jmpCodeSize) + { + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta new file mode 100644 index 00000000..f7453504 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97cc0d26f72fc4148b8370b2252d1585 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs new file mode 100644 index 00000000..34a05992 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs @@ -0,0 +1,74 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using System.Linq; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace MonoHook +{ + /// + /// Hook 池,防止重复 Hook + /// + public static class HookPool + { + private static Dictionary _hooks = new Dictionary(); + + public static void AddHook(MethodBase method, MethodHook hook) + { + MethodHook preHook; + if (_hooks.TryGetValue(method, out preHook)) + { + preHook.Uninstall(); + _hooks[method] = hook; + } + else + _hooks.Add(method, hook); + } + + public static MethodHook GetHook(MethodBase method) + { + if (method == null) return null; + + MethodHook hook; + if (_hooks.TryGetValue(method, out hook)) + return hook; + return null; + } + + public static void RemoveHooker(MethodBase method) + { + if (method == null) return; + + _hooks.Remove(method); + } + + public static void UninstallAll() + { + var list = _hooks.Values.ToList(); + foreach (var hook in list) + hook.Uninstall(); + + _hooks.Clear(); + } + + public static void UninstallByTag(string tag) + { + var list = _hooks.Values.ToList(); + foreach (var hook in list) + { + if(hook.tag == tag) + hook.Uninstall(); + } + } + + public static List GetAllHooks() + { + return _hooks.Values.ToList(); + } + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta new file mode 100644 index 00000000..75038591 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b7421e47f0ae1e4ebb72bf18d1d7d48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs new file mode 100644 index 00000000..07a5e5e8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs @@ -0,0 +1,272 @@ +#if !(UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace MonoHook +{ + public static unsafe class HookUtils + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void DelegateFlushICache(void* code, int size); // delegate * unmanaged[Cdecl] native_flush_cache_fun_ptr; // unsupported at C# 8.0 + + static DelegateFlushICache flush_icache; + private static readonly long _Pagesize; + + static HookUtils() + { + PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize"); + if (p_SystemPageSize == null) + throw new NotSupportedException("Unsupported runtime"); + _Pagesize = (int)p_SystemPageSize.GetValue(null, new object[0]); + SetupFlushICacheFunc(); + } + + public static void MemCpy(void* pDst, void* pSrc, int len) + { + byte* pDst_ = (byte*)pDst; + byte* pSrc_ = (byte*)pSrc; + + for (int i = 0; i < len; i++) + *pDst_++ = *pSrc_++; + } + + public static void MemCpy_Jit(void* pDst, byte[] src) + { + fixed (void* p = &src[0]) + { + MemCpy(pDst, p, src.Length); + } + } + + /// + /// set flags of address to `read write execute` + /// + public static void SetAddrFlagsToRWX(IntPtr ptr, int size) + { + if (ptr == IntPtr.Zero) + return; + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + uint oldProtect; + bool ret = VirtualProtect(ptr, (uint)size, Protection.PAGE_EXECUTE_READWRITE, out oldProtect); + UnityEngine.Debug.Assert(ret); +#else + SetMemPerms(ptr,(ulong)size,MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); +#endif + } + + public static void FlushICache(void* code, int size) + { + if (code == null) + return; + + flush_icache?.Invoke(code, size); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"flush icache at 0x{(IntPtr.Size == 4 ? (uint)code : (ulong)code):x}, size:{size}"); +#endif + } + + public static KeyValuePair GetPageAlignedAddr(long code, int size) + { + long pagesize = _Pagesize; + long startPage = (code) & ~(pagesize - 1); + long endPage = (code + size + pagesize - 1) & ~(pagesize - 1); + return new KeyValuePair(startPage, endPage); + } + + + const int PRINT_SPLIT = 4; + const int PRINT_COL_SIZE = PRINT_SPLIT * 4; + public static string HexToString(void* ptr, int size, int offset = 0) + { + Func formatAddr = (IntPtr addr__) => IntPtr.Size == 4 ? $"0x{(uint)addr__:x}" : $"0x{(ulong)addr__:x}"; + + byte* addr = (byte*)ptr; + + StringBuilder sb = new StringBuilder(1024); + sb.AppendLine($"addr:{formatAddr(new IntPtr(addr))}"); + + addr += offset; + size += Math.Abs(offset); + + int count = 0; + while (true) + { + sb.Append($"\r\n{formatAddr(new IntPtr(addr + count))}: "); + for (int i = 1; i < PRINT_COL_SIZE + 1; i++) + { + if (count >= size) + goto END; + + sb.Append($"{*(addr + count):x2}"); + if (i % PRINT_SPLIT == 0) + sb.Append(" "); + + count++; + } + } + END:; + return sb.ToString(); + } + + static void SetupFlushICacheFunc() + { + string processorType = SystemInfo.processorType.ToLowerInvariant(); + if (processorType.Contains("intel") || processorType.Contains("amd")) + return; + + if (IntPtr.Size == 4) + { + // never release, so save GCHandle is unnecessary + s_ptr_flush_icache_arm32 = GCHandle.Alloc(s_flush_icache_arm32, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer(); + SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm32), s_flush_icache_arm32.Length); + flush_icache = Marshal.GetDelegateForFunctionPointer(new IntPtr(s_ptr_flush_icache_arm32)); + } + else + { + s_ptr_flush_icache_arm64 = GCHandle.Alloc(s_flush_icache_arm64, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer(); + SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm64), s_flush_icache_arm64.Length); + flush_icache = Marshal.GetDelegateForFunctionPointer(new IntPtr(s_ptr_flush_icache_arm64)); + } + +#if ENABLE_HOOK_DEBUG + Debug.Log($"flush_icache delegate is {((flush_icache != null) ? "not " : "")}null"); +#endif + } + + + static void* s_ptr_flush_icache_arm32, s_ptr_flush_icache_arm64; + private static byte[] s_flush_icache_arm32 = new byte[] + { + // void cdecl mono_arch_flush_icache (guint8 *code, gint size) + 0x00, 0x48, 0x2D, 0xE9, // PUSH {R11,LR} + 0x0D, 0xB0, 0xA0, 0xE1, // MOV R11, SP + 0x08, 0xD0, 0x4D, 0xE2, // SUB SP, SP, #8 + 0x04, 0x00, 0x8D, 0xE5, // STR R0, [SP,#8+var_4] + 0x00, 0x10, 0x8D, 0xE5, // STR R1, [SP,#8+var_8] + 0x04, 0x00, 0x9D, 0xE5, // LDR R0, [SP,#8+var_4] + 0x04, 0x10, 0x9D, 0xE5, // LDR R1, [SP,#8+var_4] + 0x00, 0x20, 0x9D, 0xE5, // LDR R2, [SP,#8+var_8] + 0x02, 0x10, 0x81, 0xE0, // ADD R1, R1, R2 + 0x01, 0x00, 0x00, 0xEB, // BL __clear_cache + 0x0B, 0xD0, 0xA0, 0xE1, // MOV SP, R11 + 0x00, 0x88, 0xBD, 0xE8, // POP {R11,PC} + + // __clear_cache ; CODE XREF: j___clear_cache+8↑j + 0x80, 0x00, 0x2D, 0xE9, // PUSH { R7 } + 0x02, 0x70, 0x00, 0xE3, 0x0F, 0x70, 0x40, 0xE3, // MOV R7, #0xF0002 + 0x00, 0x20, 0xA0, 0xE3, // MOV R2, #0 + 0x00, 0x00, 0x00, 0xEF, // SVC 0 + 0x80, 0x00, 0xBD, 0xE8, // POP {R7} + 0x1E, 0xFF, 0x2F, 0xE1, // BX LR + }; + + private static byte[] s_flush_icache_arm64 = new byte[] // X0: code, W1: size + { + // void cdecl mono_arch_flush_icache (guint8 *code, gint size) + 0xFF, 0xC3, 0x00, 0xD1, // SUB SP, SP, #0x30 + 0xE8, 0x03, 0x7E, 0xB2, // MOV X8, #4 + 0xE0, 0x17, 0x00, 0xF9, // STR X0, [SP,#0x30+var_8] + 0xE1, 0x27, 0x00, 0xB9, // STR W1, [SP,#0x30+var_C] + 0xE0, 0x17, 0x40, 0xF9, // LDR X0, [SP,#0x30+var_8] + 0xE9, 0x27, 0x80, 0xB9, // LDRSW X9, [SP,#0x30+var_C] + 0x09, 0x00, 0x09, 0x8B, // ADD X9, X0, X9 + 0xE9, 0x0F, 0x00, 0xF9, // STR X9, [SP,#0x30+var_18] + 0xE8, 0x07, 0x00, 0xF9, // STR X8, [SP,#0x30+var_28] + 0xE8, 0x03, 0x00, 0xF9, // STR X8, [SP,#0x30+var_30] + 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8] + 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + + // loc_590 ; CODE XREF: mono_arch_flush_icache(uchar*, int)+58↓j + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18] + 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9 + 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5B8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x28, 0x7E, 0x0B, 0xD5, // SYS #3, c7, c14, #1, X8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4 + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + 0xF7, 0xFF, 0xFF, 0x17, // B loc_590 + // ; --------------------------------------------------------------------------- + + // loc_5B8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+40↑j + 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH + 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8] + 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + + // loc_5C8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+90↓j + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18] + 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9 + 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5F0 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x28, 0x75, 0x0B, 0xD5, // SYS #3, c7, c5, #1, X8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4 + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + 0xF7, 0xFF, 0xFF, 0x17, // B loc_5C8 + // ; --------------------------------------------------------------------------- + + // loc_5F0 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+78↑j + 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH + 0xDF, 0x3F, 0x03, 0xD5, // ISB + 0xFF, 0xC3, 0x00, 0x91, // ADD SP, SP, #0x30 ; '0' + 0xC0, 0x03, 0x5F, 0xD6, // RET + }; + + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + [Flags] + public enum Protection + { + PAGE_NOACCESS = 0x01, + PAGE_READONLY = 0x02, + PAGE_READWRITE = 0x04, + PAGE_WRITECOPY = 0x08, + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80, + PAGE_GUARD = 0x100, + PAGE_NOCACHE = 0x200, + PAGE_WRITECOMBINE = 0x400 + } + + [DllImport("kernel32")] + public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out uint lpflOldProtect); + +#else + [Flags] + public enum MmapProts : int { + PROT_READ = 0x1, + PROT_WRITE = 0x2, + PROT_EXEC = 0x4, + PROT_NONE = 0x0, + PROT_GROWSDOWN = 0x01000000, + PROT_GROWSUP = 0x02000000, + } + + [DllImport("libc", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern int mprotect(IntPtr start, IntPtr len, MmapProts prot); + + public static unsafe void SetMemPerms(IntPtr start, ulong len, MmapProts prot) { + var requiredAddr = GetPageAlignedAddr(start.ToInt64(), (int)len); + long startPage = requiredAddr.Key; + long endPage = requiredAddr.Value; + + if (mprotect((IntPtr) startPage, (IntPtr) (endPage - startPage), prot) != 0) + throw new Exception($"mprotect with prot:{prot} fail!"); + } +#endif + } +} + +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta new file mode 100644 index 00000000..8ec4cb3a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e84139b42a6164e4c93ce4df1be6dcfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs new file mode 100644 index 00000000..4efdb0b4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs @@ -0,0 +1,116 @@ +#if (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace MonoHook +{ + public static unsafe class HookUtils + { + static bool jit_write_protect_supported; + private static readonly long _Pagesize; + + + static HookUtils() + { + try + { + jit_write_protect_supported = pthread_jit_write_protect_supported_np() != 0; + } + catch { } + + PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize"); + if (p_SystemPageSize == null) + throw new NotSupportedException("Unsupported runtime"); + _Pagesize = (int)p_SystemPageSize.GetValue(null, new object[0]); + } + + public static void MemCpy(void* pDst, void* pSrc, int len) + { + byte* pDst_ = (byte*)pDst; + byte* pSrc_ = (byte*)pSrc; + + for (int i = 0; i < len; i++) + *pDst_++ = *pSrc_++; + } + + public static void MemCpy_Jit(void* pDst, byte[] src) + { + if (!jit_write_protect_supported) + { + fixed(void * pSrc = &src[0]) + { + MemCpy(pDst, pSrc, src.Length); + } + + return; + } + + fixed(void * p = &src[0]) + { + memcpy_jit(new IntPtr(pDst), new IntPtr(p), src.Length); + } + } + + /// + /// set flags of address to `read write execute` + /// + public static void SetAddrFlagsToRWX(IntPtr ptr, int size) { } + + public static void FlushICache(void* code, int size) { } + + public static KeyValuePair GetPageAlignedAddr(long code, int size) + { + long pagesize = _Pagesize; + long startPage = (code) & ~(pagesize - 1); + long endPage = (code + size + pagesize - 1) & ~(pagesize - 1); + return new KeyValuePair(startPage, endPage); + } + + + const int PRINT_SPLIT = 4; + const int PRINT_COL_SIZE = PRINT_SPLIT * 4; + public static string HexToString(void* ptr, int size, int offset = 0) + { + Func formatAddr = (IntPtr addr__) => IntPtr.Size == 4 ? $"0x{(uint)addr__:x}" : $"0x{(ulong)addr__:x}"; + + byte* addr = (byte*)ptr; + + StringBuilder sb = new StringBuilder(1024); + sb.AppendLine($"addr:{formatAddr(new IntPtr(addr))}"); + + addr += offset; + size += Math.Abs(offset); + + int count = 0; + while (true) + { + sb.Append($"\r\n{formatAddr(new IntPtr(addr + count))}: "); + for (int i = 1; i < PRINT_COL_SIZE + 1; i++) + { + if (count >= size) + goto END; + + sb.Append($"{*(addr + count):x2}"); + if (i % PRINT_SPLIT == 0) + sb.Append(" "); + + count++; + } + } + END:; + return sb.ToString(); + } + + [DllImport("pthread", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern int pthread_jit_write_protect_supported_np(); + + [DllImport("libMonoHookUtils_OSX", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr memcpy_jit(IntPtr dst, IntPtr src, int len); + } +} + +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta new file mode 100644 index 00000000..6ecee49e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efda6e010e5c6594081c4a62861d469f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta new file mode 100644 index 00000000..8169f8d2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d796fc01daee1964586621890988a5ae +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs new file mode 100644 index 00000000..d4945522 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs @@ -0,0 +1,67 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2021_1_OR_NEWER && !UNITY_2023_1_OR_NEWER + [InitializeOnLoad] + public class CopyStrippedAOTAssembliesHook + { + private static MethodHook _hook; + + static CopyStrippedAOTAssembliesHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication).Assembly.GetType("UnityEditorInternal.AssemblyStripper"); + MethodInfo miTarget = type.GetMethod("StripAssembliesTo", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + MethodInfo miReplacement = new StripAssembliesDel(OverrideStripAssembliesTo).Method; + MethodInfo miProxy = new StripAssembliesDel(StripAssembliesToProxy).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private delegate bool StripAssembliesDel(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation); + + private static bool OverrideStripAssembliesTo(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation) + { + bool result = StripAssembliesToProxy(outputFolder, out output, out error, linkXmlFiles, runInformation); + if (!result) + { + return false; + } + UnityEngine.Debug.Log($"== StripAssembliesTo outputDir:{outputFolder}"); + string outputStrippedDir = HybridCLR.Editor.SettingsUtil.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget); + Directory.CreateDirectory(outputStrippedDir); + foreach (var aotDll in Directory.GetFiles(outputFolder, "*.dll")) + { + string dstFile = $"{outputStrippedDir}/{Path.GetFileName(aotDll)}"; + Debug.Log($"[RunAssemblyStripper] copy aot dll {aotDll} -> {dstFile}"); + File.Copy(aotDll, dstFile, true); + } + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static bool StripAssembliesToProxy(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation) + { + Debug.LogError("== StripAssembliesToProxy =="); + output = ""; + error = ""; + return true; + } + } +#endif +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta new file mode 100644 index 00000000..18542c32 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf42c4f20b8a1b94baa04a1a5c6b8beb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs new file mode 100644 index 00000000..fbaf8bf6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2022 || UNITY_2023_1_OR_NEWER + [InitializeOnLoad] + public class GetIl2CppFolderHook + { + private static MethodHook _hook; + + static GetIl2CppFolderHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication).Assembly.GetType("UnityEditorInternal.IL2CPPUtils"); + MethodInfo miTarget = type.GetMethod("GetIl2CppFolder", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, + new Type[] { typeof(bool).MakeByRefType() }, null); + + MethodInfo miReplacement = new StripAssembliesDel(OverrideMethod).Method; + MethodInfo miProxy = new StripAssembliesDel(PlaceHolderMethod).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private delegate string StripAssembliesDel(out bool isDevelopmentLocation); + + private static string OverrideMethod(out bool isDevelopmentLocation) + { + //Debug.Log("[GetIl2CppFolderHook] OverrideMethod"); + string result = PlaceHolderMethod(out isDevelopmentLocation); + isDevelopmentLocation = false; + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static string PlaceHolderMethod(out bool isDevelopmentLocation) + { + Debug.LogError("[GetIl2CppFolderHook] PlaceHolderMethod"); + isDevelopmentLocation = false; + return null; + } + } +#endif +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta new file mode 100644 index 00000000..bcde7d8a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96c2bc28db69e1644892219abef3d4b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs new file mode 100644 index 00000000..c9127565 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs @@ -0,0 +1,74 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2021_1_OR_NEWER && (UNITY_WEBGL || UNITY_WEIXINMINIGAME) + [InitializeOnLoad] + public class PatchScriptingAssembliesJsonHook + { + private static MethodHook _hook; + + static PatchScriptingAssembliesJsonHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication); + MethodInfo miTarget = type.GetMethod("BuildMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic); + + MethodInfo miReplacement = new Func(BuildMainWindowTitle).Method; + MethodInfo miProxy = new Func(BuildMainWindowTitleProxy).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private static string BuildMainWindowTitle() + { + var cacheDir = $"{Application.dataPath}/../Library/PlayerDataCache"; + if (Directory.Exists(cacheDir)) + { + foreach (var tempJsonPath in Directory.GetDirectories(cacheDir, "*", SearchOption.TopDirectoryOnly)) + { + string dirName = Path.GetFileName(tempJsonPath); + #if UNITY_WEIXINMINIGAME + if (!dirName.Contains("WeixinMiniGame")) + { + continue; + } +#else + if (!dirName.Contains("WebGL")) + { + continue; + } +#endif + + var patcher = new PatchScriptingAssemblyList(); + patcher.PathScriptingAssembilesFile(tempJsonPath); + } + } + + string newTitle = BuildMainWindowTitleProxy(); + return newTitle; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static string BuildMainWindowTitleProxy() + { + // 为满足MonoHook要求的最小代码长度而特地加入的无用填充代码, + UnityEngine.Debug.Log(12345.ToString()); + return string.Empty; + } + } +#endif +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta new file mode 100644 index 00000000..ccf939f1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc89a9041ab48ac41975fbd1e00b9b98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs new file mode 100644 index 00000000..d87ab917 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs @@ -0,0 +1,903 @@ +using System; +using System.Runtime.InteropServices; + +namespace DotNetDetour +{ + /// + /// 用于计算汇编指令长度,使用的是BlackBone的LDasm.c中的算法,我把他翻译成C#了 + /// + public unsafe class LDasm + { + const int F_INVALID = 0x01; + const int F_PREFIX = 0x02; + const int F_REX = 0x04; + const int F_MODRM = 0x08; + const int F_SIB = 0x10; + const int F_DISP = 0x20; + const int F_IMM = 0x40; + const int F_RELATIVE = 0x80; + + const int OP_NONE = 0x00; + const int OP_INVALID = 0x80; + + const int OP_DATA_I8 = 0x01; + const int OP_DATA_I16 = 0x02; + const int OP_DATA_I16_I32 = 0x04; + const int OP_DATA_I16_I32_I64 = 0x08; + const int OP_EXTENDED = 0x10; + const int OP_RELATIVE = 0x20; + const int OP_MODRM = 0x40; + const int OP_PREFIX = 0x80; + + struct ldasm_data + { + public byte flags; + public byte rex; + public byte modrm; + public byte sib; + public byte opcd_offset; + public byte opcd_size; + public byte disp_offset; + public byte disp_size; + public byte imm_offset; + public byte imm_size; + } + + static byte[] flags_table = + { + /* 00 */ OP_MODRM, + /* 01 */ OP_MODRM, + /* 02 */ OP_MODRM, + /* 03 */ OP_MODRM, + /* 04 */ OP_DATA_I8, + /* 05 */ OP_DATA_I16_I32, + /* 06 */ OP_NONE, + /* 07 */ OP_NONE, + /* 08 */ OP_MODRM, + /* 09 */ OP_MODRM, + /* 0A */ OP_MODRM, + /* 0B */ OP_MODRM, + /* 0C */ OP_DATA_I8, + /* 0D */ OP_DATA_I16_I32, + /* 0E */ OP_NONE, + /* 0F */ OP_NONE, + + /* 10 */ OP_MODRM, + /* 11 */ OP_MODRM, + /* 12 */ OP_MODRM, + /* 13 */ OP_MODRM, + /* 14 */ OP_DATA_I8, + /* 15 */ OP_DATA_I16_I32, + /* 16 */ OP_NONE, + /* 17 */ OP_NONE, + /* 18 */ OP_MODRM, + /* 19 */ OP_MODRM, + /* 1A */ OP_MODRM, + /* 1B */ OP_MODRM, + /* 1C */ OP_DATA_I8, + /* 1D */ OP_DATA_I16_I32, + /* 1E */ OP_NONE, + /* 1F */ OP_NONE, + + /* 20 */ OP_MODRM, + /* 21 */ OP_MODRM, + /* 22 */ OP_MODRM, + /* 23 */ OP_MODRM, + /* 24 */ OP_DATA_I8, + /* 25 */ OP_DATA_I16_I32, + /* 26 */ OP_PREFIX, + /* 27 */ OP_NONE, + /* 28 */ OP_MODRM, + /* 29 */ OP_MODRM, + /* 2A */ OP_MODRM, + /* 2B */ OP_MODRM, + /* 2C */ OP_DATA_I8, + /* 2D */ OP_DATA_I16_I32, + /* 2E */ OP_PREFIX, + /* 2F */ OP_NONE, + + /* 30 */ OP_MODRM, + /* 31 */ OP_MODRM, + /* 32 */ OP_MODRM, + /* 33 */ OP_MODRM, + /* 34 */ OP_DATA_I8, + /* 35 */ OP_DATA_I16_I32, + /* 36 */ OP_PREFIX, + /* 37 */ OP_NONE, + /* 38 */ OP_MODRM, + /* 39 */ OP_MODRM, + /* 3A */ OP_MODRM, + /* 3B */ OP_MODRM, + /* 3C */ OP_DATA_I8, + /* 3D */ OP_DATA_I16_I32, + /* 3E */ OP_PREFIX, + /* 3F */ OP_NONE, + + /* 40 */ OP_NONE, + /* 41 */ OP_NONE, + /* 42 */ OP_NONE, + /* 43 */ OP_NONE, + /* 44 */ OP_NONE, + /* 45 */ OP_NONE, + /* 46 */ OP_NONE, + /* 47 */ OP_NONE, + /* 48 */ OP_NONE, + /* 49 */ OP_NONE, + /* 4A */ OP_NONE, + /* 4B */ OP_NONE, + /* 4C */ OP_NONE, + /* 4D */ OP_NONE, + /* 4E */ OP_NONE, + /* 4F */ OP_NONE, + + /* 50 */ OP_NONE, + /* 51 */ OP_NONE, + /* 52 */ OP_NONE, + /* 53 */ OP_NONE, + /* 54 */ OP_NONE, + /* 55 */ OP_NONE, + /* 56 */ OP_NONE, + /* 57 */ OP_NONE, + /* 58 */ OP_NONE, + /* 59 */ OP_NONE, + /* 5A */ OP_NONE, + /* 5B */ OP_NONE, + /* 5C */ OP_NONE, + /* 5D */ OP_NONE, + /* 5E */ OP_NONE, + /* 5F */ OP_NONE, + /* 60 */ OP_NONE, + + /* 61 */ OP_NONE, + /* 62 */ OP_MODRM, + /* 63 */ OP_MODRM, + /* 64 */ OP_PREFIX, + /* 65 */ OP_PREFIX, + /* 66 */ OP_PREFIX, + /* 67 */ OP_PREFIX, + /* 68 */ OP_DATA_I16_I32, + /* 69 */ OP_MODRM | OP_DATA_I16_I32, + /* 6A */ OP_DATA_I8, + /* 6B */ OP_MODRM | OP_DATA_I8, + /* 6C */ OP_NONE, + /* 6D */ OP_NONE, + /* 6E */ OP_NONE, + /* 6F */ OP_NONE, + + /* 70 */ OP_RELATIVE | OP_DATA_I8, + /* 71 */ OP_RELATIVE | OP_DATA_I8, + /* 72 */ OP_RELATIVE | OP_DATA_I8, + /* 73 */ OP_RELATIVE | OP_DATA_I8, + /* 74 */ OP_RELATIVE | OP_DATA_I8, + /* 75 */ OP_RELATIVE | OP_DATA_I8, + /* 76 */ OP_RELATIVE | OP_DATA_I8, + /* 77 */ OP_RELATIVE | OP_DATA_I8, + /* 78 */ OP_RELATIVE | OP_DATA_I8, + /* 79 */ OP_RELATIVE | OP_DATA_I8, + /* 7A */ OP_RELATIVE | OP_DATA_I8, + /* 7B */ OP_RELATIVE | OP_DATA_I8, + /* 7C */ OP_RELATIVE | OP_DATA_I8, + /* 7D */ OP_RELATIVE | OP_DATA_I8, + /* 7E */ OP_RELATIVE | OP_DATA_I8, + /* 7F */ OP_RELATIVE | OP_DATA_I8, + + /* 80 */ OP_MODRM | OP_DATA_I8, + /* 81 */ OP_MODRM | OP_DATA_I16_I32, + /* 82 */ OP_MODRM | OP_DATA_I8, + /* 83 */ OP_MODRM | OP_DATA_I8, + /* 84 */ OP_MODRM, + /* 85 */ OP_MODRM, + /* 86 */ OP_MODRM, + /* 87 */ OP_MODRM, + /* 88 */ OP_MODRM, + /* 89 */ OP_MODRM, + /* 8A */ OP_MODRM, + /* 8B */ OP_MODRM, + /* 8C */ OP_MODRM, + /* 8D */ OP_MODRM, + /* 8E */ OP_MODRM, + /* 8F */ OP_MODRM, + + /* 90 */ OP_NONE, + /* 91 */ OP_NONE, + /* 92 */ OP_NONE, + /* 93 */ OP_NONE, + /* 94 */ OP_NONE, + /* 95 */ OP_NONE, + /* 96 */ OP_NONE, + /* 97 */ OP_NONE, + /* 98 */ OP_NONE, + /* 99 */ OP_NONE, + /* 9A */ OP_DATA_I16 | OP_DATA_I16_I32, + /* 9B */ OP_NONE, + /* 9C */ OP_NONE, + /* 9D */ OP_NONE, + /* 9E */ OP_NONE, + /* 9F */ OP_NONE, + + /* A0 */ OP_DATA_I8, + /* A1 */ OP_DATA_I16_I32_I64, + /* A2 */ OP_DATA_I8, + /* A3 */ OP_DATA_I16_I32_I64, + /* A4 */ OP_NONE, + /* A5 */ OP_NONE, + /* A6 */ OP_NONE, + /* A7 */ OP_NONE, + /* A8 */ OP_DATA_I8, + /* A9 */ OP_DATA_I16_I32, + /* AA */ OP_NONE, + /* AB */ OP_NONE, + /* AC */ OP_NONE, + /* AD */ OP_NONE, + /* AE */ OP_NONE, + /* AF */ OP_NONE, + + /* B0 */ OP_DATA_I8, + /* B1 */ OP_DATA_I8, + /* B2 */ OP_DATA_I8, + /* B3 */ OP_DATA_I8, + /* B4 */ OP_DATA_I8, + /* B5 */ OP_DATA_I8, + /* B6 */ OP_DATA_I8, + /* B7 */ OP_DATA_I8, + /* B8 */ OP_DATA_I16_I32_I64, + /* B9 */ OP_DATA_I16_I32_I64, + /* BA */ OP_DATA_I16_I32_I64, + /* BB */ OP_DATA_I16_I32_I64, + /* BC */ OP_DATA_I16_I32_I64, + /* BD */ OP_DATA_I16_I32_I64, + /* BE */ OP_DATA_I16_I32_I64, + /* BF */ OP_DATA_I16_I32_I64, + + /* C0 */ OP_MODRM | OP_DATA_I8, + /* C1 */ OP_MODRM | OP_DATA_I8, + /* C2 */ OP_DATA_I16, + /* C3 */ OP_NONE, + /* C4 */ OP_MODRM, + /* C5 */ OP_MODRM, + /* C6 */ OP_MODRM | OP_DATA_I8, + /* C7 */ OP_MODRM | OP_DATA_I16_I32, + /* C8 */ OP_DATA_I8 | OP_DATA_I16, + /* C9 */ OP_NONE, + /* CA */ OP_DATA_I16, + /* CB */ OP_NONE, + /* CC */ OP_NONE, + /* CD */ OP_DATA_I8, + /* CE */ OP_NONE, + /* CF */ OP_NONE, + + /* D0 */ OP_MODRM, + /* D1 */ OP_MODRM, + /* D2 */ OP_MODRM, + /* D3 */ OP_MODRM, + /* D4 */ OP_DATA_I8, + /* D5 */ OP_DATA_I8, + /* D6 */ OP_NONE, + /* D7 */ OP_NONE, + /* D8 */ OP_MODRM, + /* D9 */ OP_MODRM, + /* DA */ OP_MODRM, + /* DB */ OP_MODRM, + /* DC */ OP_MODRM, + /* DD */ OP_MODRM, + /* DE */ OP_MODRM, + /* DF */ OP_MODRM, + + /* E0 */ OP_RELATIVE | OP_DATA_I8, + /* E1 */ OP_RELATIVE | OP_DATA_I8, + /* E2 */ OP_RELATIVE | OP_DATA_I8, + /* E3 */ OP_RELATIVE | OP_DATA_I8, + /* E4 */ OP_DATA_I8, + /* E5 */ OP_DATA_I8, + /* E6 */ OP_DATA_I8, + /* E7 */ OP_DATA_I8, + /* E8 */ OP_RELATIVE | OP_DATA_I16_I32, + /* E9 */ OP_RELATIVE | OP_DATA_I16_I32, + /* EA */ OP_DATA_I16 | OP_DATA_I16_I32, + /* EB */ OP_RELATIVE | OP_DATA_I8, + /* EC */ OP_NONE, + /* ED */ OP_NONE, + /* EE */ OP_NONE, + /* EF */ OP_NONE, + + /* F0 */ OP_PREFIX, + /* F1 */ OP_NONE, + /* F2 */ OP_PREFIX, + /* F3 */ OP_PREFIX, + /* F4 */ OP_NONE, + /* F5 */ OP_NONE, + /* F6 */ OP_MODRM, + /* F7 */ OP_MODRM, + /* F8 */ OP_NONE, + /* F9 */ OP_NONE, + /* FA */ OP_NONE, + /* FB */ OP_NONE, + /* FC */ OP_NONE, + /* FD */ OP_NONE, + /* FE */ OP_MODRM, + /* FF */ OP_MODRM + }; + + static byte[] flags_table_ex = + { + /* 0F00 */ OP_MODRM, + /* 0F01 */ OP_MODRM, + /* 0F02 */ OP_MODRM, + /* 0F03 */ OP_MODRM, + /* 0F04 */ OP_INVALID, + /* 0F05 */ OP_NONE, + /* 0F06 */ OP_NONE, + /* 0F07 */ OP_NONE, + /* 0F08 */ OP_NONE, + /* 0F09 */ OP_NONE, + /* 0F0A */ OP_INVALID, + /* 0F0B */ OP_NONE, + /* 0F0C */ OP_INVALID, + /* 0F0D */ OP_MODRM, + /* 0F0E */ OP_INVALID, + /* 0F0F */ OP_MODRM | OP_DATA_I8, //3Dnow + + /* 0F10 */ OP_MODRM, + /* 0F11 */ OP_MODRM, + /* 0F12 */ OP_MODRM, + /* 0F13 */ OP_MODRM, + /* 0F14 */ OP_MODRM, + /* 0F15 */ OP_MODRM, + /* 0F16 */ OP_MODRM, + /* 0F17 */ OP_MODRM, + /* 0F18 */ OP_MODRM, + /* 0F19 */ OP_INVALID, + /* 0F1A */ OP_INVALID, + /* 0F1B */ OP_INVALID, + /* 0F1C */ OP_INVALID, + /* 0F1D */ OP_INVALID, + /* 0F1E */ OP_INVALID, + /* 0F1F */ OP_NONE, + + /* 0F20 */ OP_MODRM, + /* 0F21 */ OP_MODRM, + /* 0F22 */ OP_MODRM, + /* 0F23 */ OP_MODRM, + /* 0F24 */ OP_MODRM | OP_EXTENDED, //SSE5 + /* 0F25 */ OP_INVALID, + /* 0F26 */ OP_MODRM, + /* 0F27 */ OP_INVALID, + /* 0F28 */ OP_MODRM, + /* 0F29 */ OP_MODRM, + /* 0F2A */ OP_MODRM, + /* 0F2B */ OP_MODRM, + /* 0F2C */ OP_MODRM, + /* 0F2D */ OP_MODRM, + /* 0F2E */ OP_MODRM, + /* 0F2F */ OP_MODRM, + + /* 0F30 */ OP_NONE, + /* 0F31 */ OP_NONE, + /* 0F32 */ OP_NONE, + /* 0F33 */ OP_NONE, + /* 0F34 */ OP_NONE, + /* 0F35 */ OP_NONE, + /* 0F36 */ OP_INVALID, + /* 0F37 */ OP_NONE, + /* 0F38 */ OP_MODRM | OP_EXTENDED, + /* 0F39 */ OP_INVALID, + /* 0F3A */ OP_MODRM | OP_EXTENDED | OP_DATA_I8, + /* 0F3B */ OP_INVALID, + /* 0F3C */ OP_INVALID, + /* 0F3D */ OP_INVALID, + /* 0F3E */ OP_INVALID, + /* 0F3F */ OP_INVALID, + + /* 0F40 */ OP_MODRM, + /* 0F41 */ OP_MODRM, + /* 0F42 */ OP_MODRM, + /* 0F43 */ OP_MODRM, + /* 0F44 */ OP_MODRM, + /* 0F45 */ OP_MODRM, + /* 0F46 */ OP_MODRM, + /* 0F47 */ OP_MODRM, + /* 0F48 */ OP_MODRM, + /* 0F49 */ OP_MODRM, + /* 0F4A */ OP_MODRM, + /* 0F4B */ OP_MODRM, + /* 0F4C */ OP_MODRM, + /* 0F4D */ OP_MODRM, + /* 0F4E */ OP_MODRM, + /* 0F4F */ OP_MODRM, + + /* 0F50 */ OP_MODRM, + /* 0F51 */ OP_MODRM, + /* 0F52 */ OP_MODRM, + /* 0F53 */ OP_MODRM, + /* 0F54 */ OP_MODRM, + /* 0F55 */ OP_MODRM, + /* 0F56 */ OP_MODRM, + /* 0F57 */ OP_MODRM, + /* 0F58 */ OP_MODRM, + /* 0F59 */ OP_MODRM, + /* 0F5A */ OP_MODRM, + /* 0F5B */ OP_MODRM, + /* 0F5C */ OP_MODRM, + /* 0F5D */ OP_MODRM, + /* 0F5E */ OP_MODRM, + /* 0F5F */ OP_MODRM, + + /* 0F60 */ OP_MODRM, + /* 0F61 */ OP_MODRM, + /* 0F62 */ OP_MODRM, + /* 0F63 */ OP_MODRM, + /* 0F64 */ OP_MODRM, + /* 0F65 */ OP_MODRM, + /* 0F66 */ OP_MODRM, + /* 0F67 */ OP_MODRM, + /* 0F68 */ OP_MODRM, + /* 0F69 */ OP_MODRM, + /* 0F6A */ OP_MODRM, + /* 0F6B */ OP_MODRM, + /* 0F6C */ OP_MODRM, + /* 0F6D */ OP_MODRM, + /* 0F6E */ OP_MODRM, + /* 0F6F */ OP_MODRM, + + /* 0F70 */ OP_MODRM | OP_DATA_I8, + /* 0F71 */ OP_MODRM | OP_DATA_I8, + /* 0F72 */ OP_MODRM | OP_DATA_I8, + /* 0F73 */ OP_MODRM | OP_DATA_I8, + /* 0F74 */ OP_MODRM, + /* 0F75 */ OP_MODRM, + /* 0F76 */ OP_MODRM, + /* 0F77 */ OP_NONE, + /* 0F78 */ OP_MODRM, + /* 0F79 */ OP_MODRM, + /* 0F7A */ OP_INVALID, + /* 0F7B */ OP_INVALID, + /* 0F7C */ OP_MODRM, + /* 0F7D */ OP_MODRM, + /* 0F7E */ OP_MODRM, + /* 0F7F */ OP_MODRM, + + /* 0F80 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F81 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F82 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F83 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F84 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F85 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F86 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F87 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F88 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F89 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8A */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8B */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8C */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8D */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8E */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8F */ OP_RELATIVE | OP_DATA_I16_I32, + + /* 0F90 */ OP_MODRM, + /* 0F91 */ OP_MODRM, + /* 0F92 */ OP_MODRM, + /* 0F93 */ OP_MODRM, + /* 0F94 */ OP_MODRM, + /* 0F95 */ OP_MODRM, + /* 0F96 */ OP_MODRM, + /* 0F97 */ OP_MODRM, + /* 0F98 */ OP_MODRM, + /* 0F99 */ OP_MODRM, + /* 0F9A */ OP_MODRM, + /* 0F9B */ OP_MODRM, + /* 0F9C */ OP_MODRM, + /* 0F9D */ OP_MODRM, + /* 0F9E */ OP_MODRM, + /* 0F9F */ OP_MODRM, + + /* 0FA0 */ OP_NONE, + /* 0FA1 */ OP_NONE, + /* 0FA2 */ OP_NONE, + /* 0FA3 */ OP_MODRM, + /* 0FA4 */ OP_MODRM | OP_DATA_I8, + /* 0FA5 */ OP_MODRM, + /* 0FA6 */ OP_INVALID, + /* 0FA7 */ OP_INVALID, + /* 0FA8 */ OP_NONE, + /* 0FA9 */ OP_NONE, + /* 0FAA */ OP_NONE, + /* 0FAB */ OP_MODRM, + /* 0FAC */ OP_MODRM | OP_DATA_I8, + /* 0FAD */ OP_MODRM, + /* 0FAE */ OP_MODRM, + /* 0FAF */ OP_MODRM, + + /* 0FB0 */ OP_MODRM, + /* 0FB1 */ OP_MODRM, + /* 0FB2 */ OP_MODRM, + /* 0FB3 */ OP_MODRM, + /* 0FB4 */ OP_MODRM, + /* 0FB5 */ OP_MODRM, + /* 0FB6 */ OP_MODRM, + /* 0FB7 */ OP_MODRM, + /* 0FB8 */ OP_MODRM, + /* 0FB9 */ OP_MODRM, + /* 0FBA */ OP_MODRM | OP_DATA_I8, + /* 0FBB */ OP_MODRM, + /* 0FBC */ OP_MODRM, + /* 0FBD */ OP_MODRM, + /* 0FBE */ OP_MODRM, + /* 0FBF */ OP_MODRM, + + /* 0FC0 */ OP_MODRM, + /* 0FC1 */ OP_MODRM, + /* 0FC2 */ OP_MODRM | OP_DATA_I8, + /* 0FC3 */ OP_MODRM, + /* 0FC4 */ OP_MODRM | OP_DATA_I8, + /* 0FC5 */ OP_MODRM | OP_DATA_I8, + /* 0FC6 */ OP_MODRM | OP_DATA_I8, + /* 0FC7 */ OP_MODRM, + /* 0FC8 */ OP_NONE, + /* 0FC9 */ OP_NONE, + /* 0FCA */ OP_NONE, + /* 0FCB */ OP_NONE, + /* 0FCC */ OP_NONE, + /* 0FCD */ OP_NONE, + /* 0FCE */ OP_NONE, + /* 0FCF */ OP_NONE, + + /* 0FD0 */ OP_MODRM, + /* 0FD1 */ OP_MODRM, + /* 0FD2 */ OP_MODRM, + /* 0FD3 */ OP_MODRM, + /* 0FD4 */ OP_MODRM, + /* 0FD5 */ OP_MODRM, + /* 0FD6 */ OP_MODRM, + /* 0FD7 */ OP_MODRM, + /* 0FD8 */ OP_MODRM, + /* 0FD9 */ OP_MODRM, + /* 0FDA */ OP_MODRM, + /* 0FDB */ OP_MODRM, + /* 0FDC */ OP_MODRM, + /* 0FDD */ OP_MODRM, + /* 0FDE */ OP_MODRM, + /* 0FDF */ OP_MODRM, + + /* 0FE0 */ OP_MODRM, + /* 0FE1 */ OP_MODRM, + /* 0FE2 */ OP_MODRM, + /* 0FE3 */ OP_MODRM, + /* 0FE4 */ OP_MODRM, + /* 0FE5 */ OP_MODRM, + /* 0FE6 */ OP_MODRM, + /* 0FE7 */ OP_MODRM, + /* 0FE8 */ OP_MODRM, + /* 0FE9 */ OP_MODRM, + /* 0FEA */ OP_MODRM, + /* 0FEB */ OP_MODRM, + /* 0FEC */ OP_MODRM, + /* 0FED */ OP_MODRM, + /* 0FEE */ OP_MODRM, + /* 0FEF */ OP_MODRM, + + /* 0FF0 */ OP_MODRM, + /* 0FF1 */ OP_MODRM, + /* 0FF2 */ OP_MODRM, + /* 0FF3 */ OP_MODRM, + /* 0FF4 */ OP_MODRM, + /* 0FF5 */ OP_MODRM, + /* 0FF6 */ OP_MODRM, + /* 0FF7 */ OP_MODRM, + /* 0FF8 */ OP_MODRM, + /* 0FF9 */ OP_MODRM, + /* 0FFA */ OP_MODRM, + /* 0FFB */ OP_MODRM, + /* 0FFC */ OP_MODRM, + /* 0FFD */ OP_MODRM, + /* 0FFE */ OP_MODRM, + /* 0FFF */ OP_INVALID, + }; + + static byte cflags(byte op) + { + return flags_table[op]; + } + + static byte cflags_ex(byte op) + { + return flags_table_ex[op]; + } + + /// + /// 计算大于等于 size 字节的最少指令的长度 + /// + /// + /// + public static uint SizeofMinNumByte(void* code, int size) + { + if (IsARM()) + return (uint)((size + 3) / 4) * 4; // 此为 jit 模式下的长度,不再支持 thumb + + uint Length; + byte* pOpcode; + uint Result = 0; + ldasm_data data = new ldasm_data(); + bool is64 = IntPtr.Size == 8; + do + { + Length = ldasm(code, data, is64); + + pOpcode = (byte*)code + data.opcd_offset; + Result += Length; + if (Result >= size) + break; + if ((Length == 1) && (*pOpcode == 0xCC)) + break; + + code = (void*)((ulong)code + Length); + + } while (Length>0); + + return Result; + } + + static bool? s_isArm; + public static bool IsARM() + { + if(s_isArm.HasValue) + return s_isArm.Value; + + var arch = RuntimeInformation.ProcessArchitecture; + s_isArm = arch == Architecture.Arm || arch == Architecture.Arm64; + + return s_isArm.Value; + } + + public static bool IsArm32() + { + return IsARM() && IntPtr.Size == 4; + } + + public static bool IsArm64() + { + return IsARM() && IntPtr.Size == 8; + } + + static bool? s_isiOS; + public static bool IsiOS() + { + if(s_isiOS.HasValue) + return s_isiOS.Value; + + s_isiOS = UnityEngine.SystemInfo.operatingSystem.ToLower().Contains("ios"); + return s_isiOS.Value; + } + + static bool? s_isIL2CPP; + public static bool IsIL2CPP() + { + if (s_isIL2CPP.HasValue) + return s_isIL2CPP.Value; + + try + { + byte[] ilBody = typeof(LDasm).GetMethod("IsIL2CPP").GetMethodBody().GetILAsByteArray(); + if (ilBody == null || ilBody.Length == 0) + s_isIL2CPP = true; + else + s_isIL2CPP = false; + } + catch + { + s_isIL2CPP = true; + } + + return s_isIL2CPP.Value; + } + + public static bool IsThumb(IntPtr code) + { + return IsArm32() && ((long)code & 0x1) == 0x1; + } + + /// + /// 计算 thumb 指令长度 + /// + /// + /// + /// + public static uint CalcARMThumbMinLen(void* code, int size) + { + uint len = 0; + + ushort* ins = (ushort*)code; + while (true) + { + if (len >= size) + return len; + + if (((*ins >> 13) & 3) == 3) + { + ins += 2; + len += 4; + } + else + { + ins++; + len += 2; + } + } + } + + static uint ldasm(void* code, ldasm_data ld, bool is64) + { + byte* p = (byte*)code; + byte s, op, f; + byte rexw, pr_66, pr_67; + + s = rexw = pr_66 = pr_67 = 0; + + /* dummy check */ + if ((int)code==0) + return 0; + + /* init output data */ + //memset(ld, 0, sizeof(ldasm_data)); + + /* phase 1: parse prefixies */ + while ((cflags(*p) & OP_PREFIX)!=0) + { + if (*p == 0x66) + pr_66 = 1; + if (*p == 0x67) + pr_67 = 1; + p++; s++; + ld.flags |= F_PREFIX; + if (s == 15) + { + ld.flags |= F_INVALID; + return s; + } + } + + /* parse REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.rex = *p; + rexw = (byte)((ld.rex >> 3) & 1); + ld.flags |= F_REX; + p++; s++; + } + + /* can be only one REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.flags |= F_INVALID; + s++; + return s; + } + + /* phase 2: parse opcode */ + ld.opcd_offset = (byte)(p - (byte*)code); + ld.opcd_size = 1; + op = *p++; s++; + + /* is 2 byte opcode? */ + if (op == 0x0F) + { + op = *p++; s++; + ld.opcd_size++; + f = cflags_ex(op); + if ((f & OP_INVALID)!=0) + { + ld.flags |= F_INVALID; + return s; + } + /* for SSE instructions */ + if ((f & OP_EXTENDED)!=0) + { + op = *p++; s++; + ld.opcd_size++; + } + } + else { + f = cflags(op); + /* pr_66 = pr_67 for opcodes A0-A3 */ + if (op >= 0xA0 && op <= 0xA3) + pr_66 = pr_67; + } + + /* phase 3: parse ModR/M, SIB and DISP */ + if ((f & OP_MODRM)!=0) + { + byte mod = (byte)(*p >> 6); + byte ro = (byte)((*p & 0x38) >> 3); + byte rm = (byte)(*p & 7); + + ld.modrm = *p++; s++; + ld.flags |= F_MODRM; + + /* in F6,F7 opcodes immediate data present if R/O == 0 */ + if (op == 0xF6 && (ro == 0 || ro == 1)) + f |= OP_DATA_I8; + if (op == 0xF7 && (ro == 0 || ro == 1)) + f |= OP_DATA_I16_I32_I64; + + /* is SIB byte exist? */ + if (mod != 3 && rm == 4 && !(!is64 && pr_67!=0)) + { + ld.sib = *p++; s++; + ld.flags |= F_SIB; + + /* if base == 5 and mod == 0 */ + if ((ld.sib & 7) == 5 && mod == 0) + { + ld.disp_size = 4; + } + } + + switch (mod) + { + case 0: + if (is64) + { + if (rm == 5) + { + ld.disp_size = 4; + if (is64) + ld.flags |= F_RELATIVE; + } + } + else if (pr_67!=0) + { + if (rm == 6) + ld.disp_size = 2; + } + else { + if (rm == 5) + ld.disp_size = 4; + } + break; + case 1: + ld.disp_size = 1; + break; + case 2: + if (is64) + ld.disp_size = 4; + else if (pr_67!=0) + ld.disp_size = 2; + else + ld.disp_size = 4; + break; + } + + if (ld.disp_size>0) + { + ld.disp_offset = (byte)(p - (byte*)code); + p += ld.disp_size; + s += ld.disp_size; + ld.flags |= F_DISP; + } + } + + /* phase 4: parse immediate data */ + if (rexw!=0 && (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = 8; + else if ((f & OP_DATA_I16_I32)!=0 || (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = (byte)(4 - (pr_66 << 1)); + + /* if exist, add OP_DATA_I16 and OP_DATA_I8 size */ + ld.imm_size += (byte)(f & 3); + + if ((ld.imm_size)!=0) + { + s += ld.imm_size; + ld.imm_offset = (byte)(p - (byte*)code); + ld.flags |= F_IMM; + if ((f & OP_RELATIVE)!=0) + ld.flags |= F_RELATIVE; + } + + /* instruction is too long */ + if (s > 15) + ld.flags |= F_INVALID; + + return s; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta new file mode 100644 index 00000000..969a5a17 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c561c9729c367e4fbef63f4ec56f268 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs new file mode 100644 index 00000000..2c7c8623 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs @@ -0,0 +1,381 @@ +/* + Desc: 一个可以运行时 Hook Mono 方法的工具,让你可以无需修改 UnityEditor.dll 等文件就可以重写其函数功能 + Author: Misaka Mikoto + Github: https://github.com/Misaka-Mikoto-Tech/MonoHook + */ + +using DotNetDetour; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using Unity.Collections.LowLevel.Unsafe; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; +using System.Runtime.CompilerServices; + + +/* +>>>>>>> 原始 UnityEditor.LogEntries.Clear 一型(.net 4.x) +0000000000403A00 < | 55 | push rbp | +0000000000403A01 | 48 8B EC | mov rbp,rsp | +0000000000403A04 | 48 81 EC 80 00 00 00 | sub rsp,80 | +0000000000403A0B | 48 89 65 B0 | mov qword ptr ss:[rbp-50],rsp | +0000000000403A0F | 48 89 6D A8 | mov qword ptr ss:[rbp-58],rbp | +0000000000403A13 | 48 89 5D C8 | mov qword ptr ss:[rbp-38],rbx | << +0000000000403A17 | 48 89 75 D0 | mov qword ptr ss:[rbp-30],rsi | +0000000000403A1B | 48 89 7D D8 | mov qword ptr ss:[rbp-28],rdi | +0000000000403A1F | 4C 89 65 E0 | mov qword ptr ss:[rbp-20],r12 | +0000000000403A23 | 4C 89 6D E8 | mov qword ptr ss:[rbp-18],r13 | +0000000000403A27 | 4C 89 75 F0 | mov qword ptr ss:[rbp-10],r14 | +0000000000403A2B | 4C 89 7D F8 | mov qword ptr ss:[rbp-8],r15 | +0000000000403A2F | 49 BB 00 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D00 | +0000000000403A39 | 4C 89 5D B8 | mov qword ptr ss:[rbp-48],r11 | +0000000000403A3D | 49 BB 08 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D08 | + + +>>>>>>> 二型(.net 2.x) +0000000000403E8F | 55 | push rbp | +0000000000403E90 | 48 8B EC | mov rbp,rsp | +0000000000403E93 | 48 83 EC 70 | sub rsp,70 | +0000000000403E97 | 48 89 65 C8 | mov qword ptr ss:[rbp-38],rsp | +0000000000403E9B | 48 89 5D B8 | mov qword ptr ss:[rbp-48],rbx | +0000000000403E9F | 48 89 6D C0 | mov qword ptr ss:[rbp-40],rbp | <<(16) +0000000000403EA3 | 48 89 75 F8 | mov qword ptr ss:[rbp-8],rsi | +0000000000403EA7 | 48 89 7D F0 | mov qword ptr ss:[rbp-10],rdi | +0000000000403EAB | 4C 89 65 D0 | mov qword ptr ss:[rbp-30],r12 | +0000000000403EAF | 4C 89 6D D8 | mov qword ptr ss:[rbp-28],r13 | +0000000000403EB3 | 4C 89 75 E0 | mov qword ptr ss:[rbp-20],r14 | +0000000000403EB7 | 4C 89 7D E8 | mov qword ptr ss:[rbp-18],r15 | +0000000000403EBB | 48 83 EC 20 | sub rsp,20 | +0000000000403EBF | 49 BB 18 3F 15 13 FE 7F 00 00 | mov r11,7FFE13153F18 | +0000000000403EC9 | 41 FF D3 | call r11 | +0000000000403ECC | 48 83 C4 20 | add rsp,20 | + +>>>>>>>>> arm64 +il2cpp:00000000003DE714 F5 0F 1D F8 STR X21, [SP,#-0x10+var_20]! | << absolute safe +il2cpp:00000000003DE718 F4 4F 01 A9 STP X20, X19, [SP,#0x20+var_10] | << may be safe +il2cpp:00000000003DE71C FD 7B 02 A9 STP X29, X30, [SP,#0x20+var_s0] | +il2cpp:00000000003DE720 FD 83 00 91 ADD X29, SP, #0x20 | +il2cpp:00000000003DE724 B5 30 00 B0 ADRP X21, #_ZZ62GameObject_SetActive_mCF1EEF2A314F3AE | << dangerous: relative instruction, can not be overwritten +il2cpp:00000000003DE728 A2 56 47 F9 LDR method, [X21,#_ZZ62GameObject_SetActive_mCF] ; | +il2cpp:00000000003DE72C F3 03 01 2A MOV W19, W1 | + */ + +namespace MonoHook +{ + /// + /// Hook 类,用来 Hook 某个 C# 方法 + /// + public unsafe class MethodHook + { + public string tag; + public bool isHooked { get; private set; } + public bool isPlayModeHook { get; private set; } + + public MethodBase targetMethod { get; private set; } // 需要被hook的目标方法 + public MethodBase replacementMethod { get; private set; } // 被hook后的替代方法 + public MethodBase proxyMethod { get; private set; } // 目标方法的代理方法(可以通过此方法调用被hook后的原方法) + + private IntPtr _targetPtr; // 目标方法被 jit 后的地址指针 + private IntPtr _replacementPtr; + private IntPtr _proxyPtr; + + private CodePatcher _codePatcher; + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + /// + /// call `MethodInfo.MethodHandle.GetFunctionPointer()` + /// will visit static class `UnityEditor.IMGUI.Controls.TreeViewGUI.Styles` and invoke its static constructor, + /// and init static filed `foldout`, but `GUISKin.current` is null now, + /// so we should wait until `GUISKin.current` has a valid value + /// + private static FieldInfo s_fi_GUISkin_current; +#endif + + static MethodHook() + { +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + s_fi_GUISkin_current = typeof(GUISkin).GetField("current", BindingFlags.Static | BindingFlags.NonPublic); +#endif + } + + /// + /// 创建一个 Hook + /// + /// 需要替换的目标方法 + /// 准备好的替换方法 + /// 如果还需要调用原始目标方法,可以通过此参数的方法调用,如果不需要可以填 null + public MethodHook(MethodBase targetMethod, MethodBase replacementMethod, MethodBase proxyMethod, string data = "") + { + this.targetMethod = targetMethod; + this.replacementMethod = replacementMethod; + this.proxyMethod = proxyMethod; + this.tag = data; + + CheckMethod(); + } + + public void Install() + { + if (LDasm.IsiOS()) // iOS 不支持修改 code 所在区域 page + return; + + if (isHooked) + return; + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + if (s_fi_GUISkin_current.GetValue(null) != null) + DoInstall(); + else + EditorApplication.update += OnEditorUpdate; +#else + DoInstall(); +#endif + isPlayModeHook = Application.isPlaying; + } + + public void Uninstall() + { + if (!isHooked) + return; + + _codePatcher.RemovePatch(); + + isHooked = false; + HookPool.RemoveHooker(targetMethod); + } + + #region private + private void DoInstall() + { + if (targetMethod == null || replacementMethod == null) + throw new Exception("none of methods targetMethod or replacementMethod can be null"); + + HookPool.AddHook(targetMethod, this); + + if (_codePatcher == null) + { + if (GetFunctionAddr()) + { +#if ENABLE_HOOK_DEBUG + UnityEngine.Debug.Log($"Original [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}"); + UnityEngine.Debug.Log($"Original [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}"); + if(proxyMethod != null) + UnityEngine.Debug.Log($"Original [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}"); +#endif + + CreateCodePatcher(); + _codePatcher.ApplyPatch(); + +#if ENABLE_HOOK_DEBUG + UnityEngine.Debug.Log($"New [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}"); + UnityEngine.Debug.Log($"New [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}"); + if(proxyMethod != null) + UnityEngine.Debug.Log($"New [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}"); +#endif + } + } + + isHooked = true; + } + + private void CheckMethod() + { + if (targetMethod == null || replacementMethod == null) + throw new Exception("MethodHook:targetMethod and replacementMethod and proxyMethod can not be null"); + + string methodName = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}"; + if (targetMethod.IsAbstract) + throw new Exception($"WRANING: you can not hook abstract method [{methodName}]"); + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + int minMethodBodySize = 10; + + { + if ((targetMethod.MethodImplementationFlags & MethodImplAttributes.InternalCall) != MethodImplAttributes.InternalCall) + { + int codeSize = targetMethod.GetMethodBody().GetILAsByteArray().Length; // GetMethodBody can not call on il2cpp + if (codeSize < minMethodBodySize) + UnityEngine.Debug.LogWarning($"WRANING: you can not hook method [{methodName}], cause its method body is too short({codeSize}), will random crash on IL2CPP release mode"); + } + } + + if(proxyMethod != null) + { + methodName = $"{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}"; + int codeSize = proxyMethod.GetMethodBody().GetILAsByteArray().Length; + if (codeSize < minMethodBodySize) + UnityEngine.Debug.LogWarning($"WRANING: size of method body[{methodName}] is too short({codeSize}), will random crash on IL2CPP release mode, please fill some dummy code inside"); + + if ((proxyMethod.MethodImplementationFlags & MethodImplAttributes.NoOptimization) != MethodImplAttributes.NoOptimization) + throw new Exception($"WRANING: method [{methodName}] must has a Attribute `MethodImpl(MethodImplOptions.NoOptimization)` to prevent code call to this optimized by compiler(pass args by shared stack)"); + } +#endif + } + + private void CreateCodePatcher() + { + long addrOffset = Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64()); + + if(_proxyPtr != IntPtr.Zero) + addrOffset = Math.Max(addrOffset, Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64())); + + if (LDasm.IsARM()) + { + if (IntPtr.Size == 8) + _codePatcher = new CodePatcher_arm64_near(_targetPtr, _replacementPtr, _proxyPtr); + else if (addrOffset < ((1 << 25) - 1)) + _codePatcher = new CodePatcher_arm32_near(_targetPtr, _replacementPtr, _proxyPtr); + else if (addrOffset < ((1 << 27) - 1)) + _codePatcher = new CodePatcher_arm32_far(_targetPtr, _replacementPtr, _proxyPtr); + else + throw new Exception("address of target method and replacement method are too far, can not hook"); + } + else + { + if (IntPtr.Size == 8) + { + if(addrOffset < 0x7fffffff) // 2G + _codePatcher = new CodePatcher_x64_near(_targetPtr, _replacementPtr, _proxyPtr); + else + _codePatcher = new CodePatcher_x64_far(_targetPtr, _replacementPtr, _proxyPtr); + } + else + _codePatcher = new CodePatcher_x86(_targetPtr, _replacementPtr, _proxyPtr); + } + } + + /// + /// 获取对应函数jit后的native code的地址 + /// + private bool GetFunctionAddr() + { + _targetPtr = GetFunctionAddr(targetMethod); + _replacementPtr = GetFunctionAddr(replacementMethod); + _proxyPtr = GetFunctionAddr(proxyMethod); + + if (_targetPtr == IntPtr.Zero || _replacementPtr == IntPtr.Zero) + return false; + + if (proxyMethod != null && _proxyPtr == null) + return false; + + if(_replacementPtr == _targetPtr) + { + throw new Exception($"the addresses of target method {targetMethod.Name} and replacement method {replacementMethod.Name} can not be same"); + } + + if (LDasm.IsThumb(_targetPtr) || LDasm.IsThumb(_replacementPtr)) + { + throw new Exception("does not support thumb arch"); + } + + return true; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 1)] // 好像在 IL2CPP 里无效 + private struct __ForCopy + { + public long __dummy; + public MethodBase method; + } + /// + /// 获取方法指令地址 + /// + /// + /// + private IntPtr GetFunctionAddr(MethodBase method) + { + if (method == null) + return IntPtr.Zero; + + if (!LDasm.IsIL2CPP()) + return method.MethodHandle.GetFunctionPointer(); + else + { + /* + // System.Reflection.MonoMethod + typedef struct Il2CppReflectionMethod + { + Il2CppObject object; + const MethodInfo *method; + Il2CppString *name; + Il2CppReflectionType *reftype; + } Il2CppReflectionMethod; + + typedef Il2CppClass Il2CppVTable; + typedef struct Il2CppObject + { + union + { + Il2CppClass *klass; + Il2CppVTable *vtable; + }; + MonitorData *monitor; + } Il2CppObject; + + typedef struct MethodInfo + { + Il2CppMethodPointer methodPointer; // this is the pointer to native code of method + InvokerMethod invoker_method; + const char* name; + Il2CppClass *klass; + const Il2CppType *return_type; + const ParameterInfo* parameters; + // ... + } + */ + + __ForCopy __forCopy = new __ForCopy() { method = method }; + + long* ptr = &__forCopy.__dummy; + ptr++; // addr of _forCopy.method + + IntPtr methodAddr = IntPtr.Zero; + if (sizeof(IntPtr) == 8) + { + long methodDataAddr = *(long*)ptr; + byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method; + + long methodPtr = 0; + methodPtr = *(long*)ptrData; + methodAddr = new IntPtr(*(long*)methodPtr); // MethodInfo::Il2CppMethodPointer methodPointer; + } + else + { + int methodDataAddr = *(int*)ptr; + byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method; + + int methodPtr = 0; + methodPtr = *(int*)ptrData; + methodAddr = new IntPtr(*(int*)methodPtr); + } + return methodAddr; + } + } + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + private void OnEditorUpdate() + { + if (s_fi_GUISkin_current.GetValue(null) != null) + { + try + { + DoInstall(); + } + finally + { + EditorApplication.update -= OnEditorUpdate; + } + } + } +#endif + + #endregion + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta new file mode 100644 index 00000000..007e62ca --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd0b8071cf434d6498160259e3829980 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta new file mode 100644 index 00000000..1f7f284e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16b9dc031f67b4fe5ad79c230f75768c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp new file mode 100644 index 00000000..106d9907 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp @@ -0,0 +1,23 @@ +// +// Utils.cpp +// MonoHookUtils_OSX +// +// Created by Misaka-Mikoto on 2022/8/31. +// +#include +#include +#include +#include +#include + +extern "C"{ + +void* memcpy_jit(void* dst, void* src, int32_t size) +{ + pthread_jit_write_protect_np(0); + void* ret = memcpy(dst, src, size); + pthread_jit_write_protect_np(1); + sys_icache_invalidate (dst, size); + return ret; +} +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta new file mode 100644 index 00000000..447c3dbb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: 56b28b5583a184c669dcb968d175544c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: ARM64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh new file mode 100644 index 00000000..8de985c0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +clang -shared -undefined dynamic_lookup -o libMonoHookUtils_OSX.dylib Utils.cpp + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta new file mode 100644 index 00000000..ed9f26dd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 69eeb734e262a0a4fbe0887249198f73 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta new file mode 100644 index 00000000..2e095626 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7adba4475cf0bdc4fa7995c0d748f480 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib new file mode 100644 index 0000000000000000000000000000000000000000..edabc8a37ed744ceabe53717d4bb64b6076800e1 GIT binary patch literal 33543 zcmeI*UuYaf90%~ZyIhkrF*!6ugW4QY3XPg#TQD!;UjIl!Z7!Iry$9_iy}Pz+a<}1j z(>A8q3nicl2*nf&{-F_TIINI{DkrE!Dk`O*hhVir$&1x)8_ z{Z!47hO&c{R^qUw3Y3(OxA{@0tw(j?vRCEpq(WreMJY{daId*iRX*N?8gH%Y!DaV& zRcbt1yB(w)OdZlwajB>F8B|j~UQUhIuKIJ?J)WvwN!aZqMJcz1A|bgo+}Z8uX{yT& zQugA=FNEKKl=}@A+YQINi~8C@``4Vy{7y^nfXh5DUW1g6cXaO9zOAG5PBT==L9?%- zq+O|Q=K0tkiE6%$(l(0xTz7rHzIUgQGTIHJZkBztaJ)RutZ5 zC%?*-U+v_1OYl8vUhb6lPxIb++GlOY6KdZ&mw68}?`96D-OW_wE!GqQ8E1=eAj9`l z$kgc?(L)F6DXsP~C;&)F?1vCoOGrw8-D z4mi|Nq~gld8_UZ4{{weBcjSK^l-66y2TPN;pL^>IS-es^OHZD19#2;x8mNx7UJ_pV z{est?hDr5NyH$v4Ax3(FUi)ZyRrOzqw$eyP&F?f)M!RA3?XvWKQ--^v>dg6;Z@MlY z4gdaKXnAdN|KL}jJou15fBL73Pk-O=e%EJv&iwe!(K9nqc4j6 z+>vKTFMl}R{=x%OH*0VC_Qk~y(!`7wHHGeIUHJII$mjX#w;MX{e6wlYpVjkYAI;rt{9A4JJoZ%akAr_Lj8!i*dPbWf W7q^(>zs7#K@7+hlmBR)9j(-4!MS4E~ literal 0 HcmV?d00001 diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta new file mode 100644 index 00000000..3045ab9e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: e092a73910a69894daea44290d7292f6 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: ARM64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta new file mode 100644 index 00000000..d4267c38 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 31f6a810e38e66f4c832b135770a04bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib new file mode 100644 index 0000000000000000000000000000000000000000..07f9063d92b36f9dbcdc9e518b52ea85f4516eb7 GIT binary patch literal 166928 zcmeI*e{dA%dBE{kcLFI0gzR_>cI==q5RZSLli&b(#*Kv$^$|cK7MjX++V%ZdS%*%y zr@cFn2-NZrJf`4U?zjwvObT&=>zFuBnwhxmI4Gg9Q@0Zj?i4$2O6VVDV2Y>2o;rfD zQJ-gbPdQGNndy{HI{m&g@4oNzzVAM}``Ly6Uft`L|LOV)0&=55>%G9ih&@O6h>=xFl|`{d#Dfu(p2VaxYwWS z36^MFI@cSjHY355*H~7*P)y_}hbr^+1^KQIR%k4bFf-fCks4h4O$nR0){?ACNPWu>#d&VP~bXpk=!{HVnB+Wt@y$ffI~uj{mYBB**tv!s@LN_a)d$@ywtSw1BLdYye;t$V-! z=(26QmmRyU^RIt!(^460l&ZPCtA=NLec$}U&>b?W-)E`lA0E-?(_G#+GVccY@1)B3 zPQFm`&O4i00u^1~TDCr%S}a9Z4*6L*N3Z+DnD2Od5UZ1xTczrJ!*MyR zl}bSX0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009IL_+J(1j_v-}*v|9KvE4hyW4ntNc6Xm|nK;`n_Ym8o zBRk%X?I~V}CA-g`)vcnjJ!{^M?Ot>GnC@#mtD8l=Wy#|Cvp4$Fr(-++rup}dPb~i9 zHe)6hKjR(u$*%p`iWExI69w0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_Kd{~rR^ zEi_&Fq}R8lhBGNEUnnN5L^_+YUGs%OGiqF!u?FmcWNuiaT8VOnKG%+?tj+0ywbe}* zY%AwF1v^==vN>JTAZzl&c`KcaC;MzGo!t`8q*L*NEtAGn1=;j;GdFBT|GT8UdKjICWerZyE*E=4lh`%mzZ@9{w{MqTGE~t{)B!`CJ?-&e9e^FVi^NXbRNY$co zhaAe|lnw?ZB<);@OZn<0mQaVia-$r}<7h>^t|Fc#ah=rJQs+o5k0msXrrf6_|MBM2 zXis^emgNoQ-1HWG8XY8 zPsDiLrerQxcH;VU!i~Gbk#>8FopExu8|g_pDO+N>hMj7U7vfV>E$K}U7gBcbw)kMK zFPrU4WC!~Ovu@u|y6E;t?klD_CDBZABE&930`wkuOJ{A)?B5O3> zVnWqAuiwH+srm_NUta6+hQ^ThiY2^SX`5V-NhhYbFIg@ndUrIsyfwNqy0RtN+R}3O zvZX6q6RGBSq9xU9C!1F^FN-G3{^7>aju-6u;?Uk1pFi}{109=3j=gsCvrER`TzO>P z>Whz0US^ukynSEW^yuF{wqyRk{dwwS`bXEzIeF%ePlkRPN}T@5+s_@n^owsjdi1+J z&a3Bo$Ex2taiTxe*)wBe`}ekQ+x5;Xdt$rqdhTlTM{e8j-uIt;G5g#7zxCj^A76Fu zy$_xmec<4CE`IX0nR~lm|Ma=5oqw(8SO28<-N#NH>$&{;z2DyOqkQb1_y1_?U%pru zZMpctK#-(>J%Wu)^uSuJkQX90+FlLw6Gv+3VpO9J0+O(!;lLVEetzsaE zC56%I{X)95mQ}Ea3epKmWxnyx`4bNZ^BU`qsZW+_Gcwtij%DQw#YBE`s4`#Az5bN` zc)J#j%k$L+`Q+KX;^(va?2P=1y^K`m+aBb*Cs?m>c|Lj2kzmSeEUTE^D!*E9rL(<` zj8x`(Imj0aPSW^_eC5fcukT^MRLg2#wP}^Ly0fFpe;aKwsHM$2PHFW=?6v9()^}@J zk2qzoVcL9?KP}$opD(9+MZXcvS1Nj6XQlf0RT`CcBy4kY4gnw(rNzk`>2ue>e8IFmX(SZ;=dlKyubM){#@xfH7?Kh>ofhHKBvAb%UZv- zW8IqewWWUxWB&Z)^9FIldVgHqwU){p>1ZB(4u9NV5h>=~NXALVGm%U>p>>0kbz+Xw z-(5&&@>XY8Pg81GhJ!>4MYvh}WB$*;*GfBSBg2gnZn6kcW zIA5>_n(ixy{H&a#*L`Blcf37_)k({(Qgy!JxE$6>r67O+0tg_000IagfB*srAb0v)rpV36n_4rhr^bPfAwYA zU;511nzoB)>&(SQStI*qy)}nrPWz|p+Ae}j%EmF0V zx4KtW_s&P9)%swt&s_Vu*H(AkHh5=C@bO!Gy#*W)KmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0;Mxm36a0OELqU5qXs`X1xdH+RAb7%I|u|0~PVtCGIs<=A*YyCv`!U)FwHMe@-J~a?)Q^7VG>XsSiulqH%{D z9@apL#wk4nB_!=!iA(wFC6-W!y>g=*%j0N8ysjdiC2^h9*;407EsrHMji%hEB>(Z| z(`Zk5p_b(h<=pfZeH)SPyzS;ATQ^7Aon&#q&KB~Kjjpp<-ehEhlXYT_)8Ac4XYy8O zS5L%v-KJzNS9aq1bi$3h!;yA-i=A$NA4@8GpWehY{7O1>{MEAG~bkU^otytnyM+`t!}py#Yd){ z?M&z+<(Fg~cOagL#NB}v(TM*v&K26~f9cfhF?qc&N;j;r6#ZP}eTNQsABPDYku@4` zF`;Um*KgsZ)SjR(ul0CCW5|2OGIX`lHn|{^PE2uMvRq2^?r3y*YjkCFWlOTPrRDBr zOINlgQqA#1ORCpSHm_)27EPG_!;PaIFWB|Pp}jLcf9RzLIyR3Sd+p|DmyEx;^2of^ z7ayOz%ru?rZ~Mc$R(|IEo)>=ipU*74{~KZZ&)o4JzvupY-S$_lZ|wX^@mr4`{cex* z>bc&r>bFju=nr-F%$V5zz3tm}z4OYR*zUWYyW0FeJhZRrt#w-t{@r!k_kMHq@XuU^8Up&kI(D;`BTT*CmtTF z{-=k2=c(7O_4N;bxb80^x%E%Kf99uMmuhbLmtX$ + { + public string Sig { get; private set; } + + public MethodDef MethodDef { get; set; } + + public ReturnInfo ReturnInfo { get; set; } + + public List ParamInfos { get; set; } + + public void Init() + { + for(int i = 0; i < ParamInfos.Count; i++) + { + ParamInfos[i].Index = i; + } + Sig = CreateCallSigName(); + } + + public void TransfromSigTypes(Func transformer) + { + ReturnInfo.Type = transformer(ReturnInfo.Type, true); + foreach(var paramType in ParamInfos) + { + paramType.Type = transformer(paramType.Type, false); + } + } + + public string CreateCallSigName() + { + var n = new StringBuilder(); + n.Append(ReturnInfo.Type.CreateSigName()); + foreach(var param in ParamInfos) + { + n.Append(param.Type.CreateSigName()); + } + return n.ToString(); + } + + public string CreateInvokeSigName() + { + var n = new StringBuilder(); + n.Append(ReturnInfo.Type.CreateSigName()); + foreach (var param in ParamInfos) + { + n.Append(param.Type.CreateSigName()); + } + return n.ToString(); + } + + public override bool Equals(object obj) + { + return Equals((MethodDesc)obj); + } + + public bool Equals(MethodDesc other) + { + return Sig == other.Sig; + } + + public override int GetHashCode() + { + return Sig.GetHashCode(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta new file mode 100644 index 00000000..0a0a97a3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28e06667d06f37b4990b16f54f903a35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs new file mode 100644 index 00000000..fc2cc7f4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + + public class ParamInfo + { + public TypeInfo Type { get; set; } + + public int Index { get; set; } + + } + + public class ReturnInfo + { + public TypeInfo Type { get; set; } + + public bool IsVoid => Type.PorType == ParamOrReturnType.VOID; + + public override string ToString() + { + return Type.GetTypeName(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta new file mode 100644 index 00000000..6b4174e3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2ba16cf4bf82374c814789b6ced3abd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs new file mode 100644 index 00000000..f8a478cb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + public enum ParamOrReturnType + { + VOID, + I1, + U1, + I2, + U2, + I4, + U4, + I8, + U8, + R4, + R8, + I, + U, + TYPEDBYREF, + STRUCT, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta new file mode 100644 index 00000000..12ffd4ba --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80682e47c38a2f04f8af94d356688cf0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs new file mode 100644 index 00000000..62fdb16b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs @@ -0,0 +1,10 @@ +namespace HybridCLR.Editor.ABI +{ + public enum PlatformABI + { + Universal32, + Universal64, + Arm64, + WebGL32, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta new file mode 100644 index 00000000..bea367c5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9f06ff0612105b4ea20e0309e759e24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs new file mode 100644 index 00000000..12af7aed --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs @@ -0,0 +1,102 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class TypeCreator + { + private readonly Dictionary _typeInfoCache = new Dictionary(TypeEqualityComparer.Instance); + + private int _nextStructId = 0; + + public TypeInfo CreateTypeInfo(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (!_typeInfoCache.TryGetValue(type, out var typeInfo)) + { + typeInfo = CreateTypeInfo0(type); + _typeInfoCache.Add(type, typeInfo); + } + return typeInfo; + } + + TypeInfo CreateTypeInfo0(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (type.IsByRef) + { + return TypeInfo.s_u; + } + switch (type.ElementType) + { + case ElementType.Void: return TypeInfo.s_void; + case ElementType.Boolean: return TypeInfo.s_u1; + case ElementType.I1: return TypeInfo.s_i1; + case ElementType.U1: return TypeInfo.s_u1; + case ElementType.I2: return TypeInfo.s_i2; + case ElementType.Char: + case ElementType.U2: return TypeInfo.s_u2; + case ElementType.I4: return TypeInfo.s_i4; + case ElementType.U4: return TypeInfo.s_u4; + case ElementType.I8: return TypeInfo.s_i8; + case ElementType.U8: return TypeInfo.s_u8; + case ElementType.R4: return TypeInfo.s_r4; + case ElementType.R8: return TypeInfo.s_r8; + case ElementType.I: return TypeInfo.s_i; + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + case ElementType.Module: + case ElementType.Var: + case ElementType.MVar: + return TypeInfo.s_u; + case ElementType.TypedByRef: return TypeInfo.s_typedByRef; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function"); + } + if (typeDef.IsEnum) + { + return CreateTypeInfo(typeDef.GetEnumUnderlyingType()); + } + return CreateValueType(type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return TypeInfo.s_u; + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return CreateTypeInfo(typeDef.GetEnumUnderlyingType()); + } + return CreateValueType(type); + } + default: throw new NotSupportedException($"{type.ElementType}"); + } + } + + protected TypeInfo CreateValueType(TypeSig type) + { + return new TypeInfo(ParamOrReturnType.STRUCT, type, _nextStructId++); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta new file mode 100644 index 00000000..74528404 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b1df5760b488fa43a68843c46fda63a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs new file mode 100644 index 00000000..6d0b4043 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs @@ -0,0 +1,116 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class TypeInfo : IEquatable + { + + public static readonly TypeInfo s_void = new TypeInfo(ParamOrReturnType.VOID); + public static readonly TypeInfo s_i1 = new TypeInfo(ParamOrReturnType.I1); + public static readonly TypeInfo s_u1 = new TypeInfo(ParamOrReturnType.U1); + public static readonly TypeInfo s_i2 = new TypeInfo(ParamOrReturnType.I2); + public static readonly TypeInfo s_u2 = new TypeInfo(ParamOrReturnType.U2); + public static readonly TypeInfo s_i4 = new TypeInfo(ParamOrReturnType.I4); + public static readonly TypeInfo s_u4 = new TypeInfo(ParamOrReturnType.U4); + public static readonly TypeInfo s_i8 = new TypeInfo(ParamOrReturnType.I8); + public static readonly TypeInfo s_u8 = new TypeInfo(ParamOrReturnType.U8); + public static readonly TypeInfo s_r4 = new TypeInfo(ParamOrReturnType.R4); + public static readonly TypeInfo s_r8 = new TypeInfo(ParamOrReturnType.R8); + public static readonly TypeInfo s_i = new TypeInfo(ParamOrReturnType.I); + public static readonly TypeInfo s_u = new TypeInfo(ParamOrReturnType.U); + public static readonly TypeInfo s_typedByRef = new TypeInfo(ParamOrReturnType.TYPEDBYREF); + + public const string strTypedByRef = "typedbyref"; + + public TypeInfo(ParamOrReturnType portype, TypeSig klass = null, int typeId = 0) + { + PorType = portype; + Klass = klass; + _typeId = typeId; + } + + public ParamOrReturnType PorType { get; } + + public TypeSig Klass { get; } + + public bool IsStruct => PorType == ParamOrReturnType.STRUCT; + + public bool IsPrimitiveType => PorType <= ParamOrReturnType.U; + + private readonly int _typeId; + + public int TypeId => _typeId; + + public bool Equals(TypeInfo other) + { + return PorType == other.PorType && TypeEqualityComparer.Instance.Equals(Klass, other.Klass); + } + + public override bool Equals(object obj) + { + return Equals((TypeInfo)obj); + } + + public override int GetHashCode() + { + return (int)PorType * 23 + (Klass != null ? TypeEqualityComparer.Instance.GetHashCode(Klass) : 0); + } + + public bool NeedExpandValue() + { + return PorType >= ParamOrReturnType.I1 && PorType <= ParamOrReturnType.U2; + } + + public string CreateSigName() + { + switch (PorType) + { + case ParamOrReturnType.VOID: return "v"; + case ParamOrReturnType.I1: return "i1"; + case ParamOrReturnType.U1: return "u1"; + case ParamOrReturnType.I2: return "i2"; + case ParamOrReturnType.U2: return "u2"; + case ParamOrReturnType.I4: return "i4"; + case ParamOrReturnType.U4: return "u4"; + case ParamOrReturnType.I8: return "i8"; + case ParamOrReturnType.U8: return "u8"; + case ParamOrReturnType.R4: return "r4"; + case ParamOrReturnType.R8: return "r8"; + case ParamOrReturnType.I: return "i"; + case ParamOrReturnType.U: return "u"; + case ParamOrReturnType.TYPEDBYREF: return strTypedByRef; + case ParamOrReturnType.STRUCT: return $"s{_typeId}"; + default: throw new NotSupportedException(PorType.ToString()); + }; + } + + public string GetTypeName() + { + switch (PorType) + { + case ParamOrReturnType.VOID: return "void"; + case ParamOrReturnType.I1: return "int8_t"; + case ParamOrReturnType.U1: return "uint8_t"; + case ParamOrReturnType.I2: return "int16_t"; + case ParamOrReturnType.U2: return "uint16_t"; + case ParamOrReturnType.I4: return "int32_t"; + case ParamOrReturnType.U4: return "uint32_t"; + case ParamOrReturnType.I8: return "int64_t"; + case ParamOrReturnType.U8: return "uint64_t"; + case ParamOrReturnType.R4: return "float"; + case ParamOrReturnType.R8: return "double"; + case ParamOrReturnType.I: return "intptr_t"; + case ParamOrReturnType.U: return "uintptr_t"; + case ParamOrReturnType.TYPEDBYREF: return "Il2CppTypedRef"; + case ParamOrReturnType.STRUCT: return $"__struct_{_typeId}__"; + default: throw new NotImplementedException(PorType.ToString()); + }; + } + + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta new file mode 100644 index 00000000..5df85775 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ffafce7f1f0bf614d95b48ca39385377 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs new file mode 100644 index 00000000..a97dd021 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs @@ -0,0 +1,181 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class ValueTypeSizeAligmentCalculator + { + public static ValueTypeSizeAligmentCalculator Caculator64 { get; } = new ValueTypeSizeAligmentCalculator(false); + + public static ValueTypeSizeAligmentCalculator Caculator32 { get; } = new ValueTypeSizeAligmentCalculator(true); + + public ValueTypeSizeAligmentCalculator(bool arch32) + { + _referenceSize = arch32 ? 4 : 8; + } + + private readonly int _referenceSize; + + private static bool IsIgnoreField(FieldDef field) + { + var ignoreAttr = field.CustomAttributes.Where(a => a.AttributeType.FullName == "UnityEngine.Bindings.IgnoreAttribute").FirstOrDefault(); + if (ignoreAttr == null) + { + return false; + } + CANamedArgument arg = ignoreAttr.GetProperty("DoesNotContributeToSize"); + if(arg != null && (bool)arg.Value) + { + //Debug.Log($"IgnoreField.DoesNotContributeToSize = true:{field}"); + return true; + } + return false; + } + + private (int Size, int Aligment) SizeAndAligmentOfStruct(TypeSig type) + { + int totalSize = 0; + int packAligment = 8; + int maxAligment = 1; + + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + + List klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList(); + GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + ClassLayout sa = typeDef.ClassLayout; + if (sa != null && sa.PackingSize > 0) + { + packAligment = sa.PackingSize; + } + bool useSLSize = true; + foreach (FieldDef field in typeDef.Fields) + { + if (field.IsStatic) + { + continue; + } + TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + var (fs, fa) = SizeAndAligmentOf(fieldType); + fa = Math.Min(fa, packAligment); + if (fa > maxAligment) + { + maxAligment = fa; + } + if (IsIgnoreField(field)) + { + continue; + } + if (typeDef.Layout.HasFlag(dnlib.DotNet.TypeAttributes.ExplicitLayout)) + { + int offset = (int)field.FieldOffset.Value; + totalSize = Math.Max(totalSize, offset + fs); + if (sa != null && offset > sa.ClassSize) + { + useSLSize = false; + } + } + else + { + if (totalSize % fa != 0) + { + totalSize = (totalSize + fa - 1) / fa * fa; + } + totalSize += fs; + if (sa != null && totalSize > sa.ClassSize) + { + useSLSize = false; + } + } + } + if (totalSize == 0) + { + totalSize = maxAligment; + } + if (totalSize % maxAligment != 0) + { + totalSize = (totalSize + maxAligment - 1) / maxAligment * maxAligment; + } + if (sa != null && sa.ClassSize > 0) + { + if (/*sa.Value == LayoutKind.Explicit &&*/ useSLSize) + { + totalSize = (int)sa.ClassSize; + while(totalSize % maxAligment != 0) + { + maxAligment /= 2; + } + } + } + return (totalSize, maxAligment); + } + + public (int Size, int Aligment) SizeAndAligmentOf(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (type.IsByRef || !type.IsValueType || type.IsArray) + return (_referenceSize, _referenceSize); + + switch (type.ElementType) + { + case ElementType.Void: throw new NotSupportedException(type.ToString()); + case ElementType.Boolean: + case ElementType.I1: + case ElementType.U1: return (1, 1); + case ElementType.Char: + case ElementType.I2: + case ElementType.U2: return (2, 2); + case ElementType.I4: + case ElementType.U4: return (4, 4); + case ElementType.I8: + case ElementType.U8: return (8, 8); + case ElementType.R4: return (4, 4); + case ElementType.R8: return (8, 8); + case ElementType.I: + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + case ElementType.Module: return (_referenceSize, _referenceSize); + case ElementType.TypedByRef: return SizeAndAligmentOfStruct(type); + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return SizeAndAligmentOf(typeDef.GetEnumUnderlyingType()); + } + return SizeAndAligmentOfStruct(type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return (_referenceSize, _referenceSize); + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return SizeAndAligmentOf(typeDef.GetEnumUnderlyingType()); + } + return SizeAndAligmentOfStruct(type); + } + default: throw new NotSupportedException(type.ToString()); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta new file mode 100644 index 00000000..4b83d636 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7af32bdf1cf55c42bfc449820d401cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta new file mode 100644 index 00000000..9b9539f8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b4071bf66ac9c544487ae88b5ee9b20a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs new file mode 100644 index 00000000..37c8952d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs @@ -0,0 +1,56 @@ +using dnlib.DotNet; +using dnlib.DotNet.Writer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.AOT +{ + public class AOTAssemblyMetadataStripper + { + public static byte[] Strip(byte[] assemblyBytes) + { + var context = ModuleDef.CreateModuleContext(); + var readerOption = new ModuleCreationOptions(context) + { + Runtime = CLRRuntimeReaderKind.Mono + }; + var mod = ModuleDefMD.Load(assemblyBytes, readerOption); + // remove all resources + mod.Resources.Clear(); + foreach (var type in mod.GetTypes()) + { + if (type.HasGenericParameters) + { + continue; + } + foreach (var method in type.Methods) + { + if (!method.HasBody || method.HasGenericParameters) + { + continue; + } + method.Body = null; + } + } + var writer = new System.IO.MemoryStream(); + var options = new ModuleWriterOptions(mod); + options.MetadataOptions.Flags |= MetadataFlags.PreserveRids; + mod.Write(writer, options); + writer.Flush(); + return writer.ToArray(); + } + + public static void Strip(string originalAssemblyPath, string strippedAssemblyPath) + { + byte[] originDllBytes = System.IO.File.ReadAllBytes(originalAssemblyPath); + byte[] strippedDllBytes = Strip(originDllBytes); + UnityEngine.Debug.Log($"aot dll:{originalAssemblyPath}, length: {originDllBytes.Length} -> {strippedDllBytes.Length}, stripping rate:{(originDllBytes.Length - strippedDllBytes.Length)/(double)originDllBytes.Length} "); + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(strippedAssemblyPath)); + System.IO.File.WriteAllBytes(strippedAssemblyPath, strippedDllBytes); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs.meta new file mode 100644 index 00000000..f7642f98 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/AOTAssemblyMetadataStripper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e9e6a048682dcb4fab806251411f29f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs new file mode 100644 index 00000000..dcd2a87a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs @@ -0,0 +1,231 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.AOT +{ + + public class Analyzer + { + public class Options + { + public AssemblyReferenceDeepCollector Collector { get; set; } + + public int MaxIterationCount { get; set; } + + public bool ComputeAotAssembly { get; set; } + } + + private readonly int _maxInterationCount; + + private readonly AssemblyReferenceDeepCollector _assemblyCollector; + + private readonly bool _computeAotAssembly; + + private readonly HashSet _genericTypes = new HashSet(); + private readonly HashSet _genericMethods = new HashSet(); + + private List _processingMethods = new List(); + private List _newMethods = new List(); + + public IReadOnlyCollection GenericTypes => _genericTypes; + + public IReadOnlyCollection GenericMethods => _genericMethods; + + private readonly MethodReferenceAnalyzer _methodReferenceAnalyzer; + + private readonly HashSet _hotUpdateAssemblyFiles; + + public ConstraintContext ConstraintContext { get; } = new ConstraintContext(); + + public List AotGenericTypes { get; } = new List(); + + public List AotGenericMethods { get; } = new List(); + + public Analyzer(Options options) + { + _assemblyCollector = options.Collector; + _maxInterationCount = options.MaxIterationCount; + _computeAotAssembly = options.ComputeAotAssembly; + _methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod); + _hotUpdateAssemblyFiles = new HashSet(options.Collector.GetRootAssemblyNames().Select(assName => assName + ".dll")); + } + + private void TryAddAndWalkGenericType(GenericClass gc) + { + if (gc == null) + { + return; + } + gc = gc.ToGenericShare(); + if (_genericTypes.Add(gc) && NeedWalk(null, gc.Type)) + { + WalkType(gc); + } + } + + private bool NeedWalk(MethodDef callFrom, TypeDef type) + { + return _hotUpdateAssemblyFiles.Contains(type.Module.Name) || callFrom == null || callFrom.HasGenericParameters; + } + + private bool IsAotType(TypeDef type) + { + return _computeAotAssembly || !_hotUpdateAssemblyFiles.Contains(type.Module.Name); + } + + private bool IsAotGenericMethod(MethodDef method) + { + return IsAotType(method.DeclaringType) && method.HasGenericParameters; + } + + private void OnNewMethod(MethodDef methodDef, List klassGenericInst, List methodGenericInst, GenericMethod method) + { + if(method == null) + { + return; + } + if (NeedWalk(methodDef, method.Method.DeclaringType) && _genericMethods.Add(method)) + { + _newMethods.Add(method); + } + if (method.KlassInst != null) + { + TryAddAndWalkGenericType(new GenericClass(method.Method.DeclaringType, method.KlassInst)); + } + } + + private void TryAddMethodNotWalkType(GenericMethod method) + { + if (method == null) + { + return; + } + if (NeedWalk(null, method.Method.DeclaringType) && _genericMethods.Add(method)) + { + _newMethods.Add(method); + } + } + + private void WalkType(GenericClass gc) + { + //Debug.Log($"typespec:{sig} {sig.GenericType} {sig.GenericType.TypeDefOrRef.ResolveTypeDef()}"); + //Debug.Log($"== walk generic type:{new GenericInstSig(gc.Type.ToTypeSig().ToClassOrValueTypeSig(), gc.KlassInst)}"); + ITypeDefOrRef baseType = gc.Type.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass parentType = GenericClass.ResolveClass((TypeSpec)baseType, new GenericArgumentContext(gc.KlassInst, null)); + TryAddAndWalkGenericType(parentType); + } + foreach (var method in gc.Type.Methods) + { + if (method.HasGenericParameters || !method.HasBody || method.Body.Instructions == null) + { + continue; + } + var gm = new GenericMethod(method, gc.KlassInst, null).ToGenericShare(); + //Debug.Log($"add method:{gm.Method} {gm.KlassInst}"); + TryAddMethodNotWalkType(gm); + } + } + + private void WalkType(TypeDef typeDef) + { + if (typeDef.HasGenericParameters) + { + return; + } + ITypeDefOrRef baseType = typeDef.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass gc = GenericClass.ResolveClass((TypeSpec)baseType, null); + TryAddAndWalkGenericType(gc); + } + } + + private void Prepare() + { + // 将所有非泛型函数全部加入函数列表,同时立马walk这些method。 + // 后续迭代中将只遍历MethodSpec + foreach (var ass in _assemblyCollector.GetLoadedModulesOfRootAssemblies()) + { + foreach (TypeDef typeDef in ass.GetTypes()) + { + WalkType(typeDef); + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.TypeSpecTable.Rows; rid <= n; rid++) + { + var ts = ass.ResolveTypeSpec(rid); + var cs = GenericClass.ResolveClass(ts, null)?.ToGenericShare(); + if (cs != null) + { + TryAddAndWalkGenericType(cs); + } + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.MethodSpecTable.Rows; rid <= n; rid++) + { + var ms = ass.ResolveMethodSpec(rid); + var gm = GenericMethod.ResolveMethod(ms, null)?.ToGenericShare(); + TryAddMethodNotWalkType(gm); + } + } + Debug.Log($"PostPrepare genericTypes:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + + private void RecursiveCollect() + { + for (int i = 0; i < _maxInterationCount && _newMethods.Count > 0; i++) + { + var temp = _processingMethods; + _processingMethods = _newMethods; + _newMethods = temp; + _newMethods.Clear(); + + foreach (var method in _processingMethods) + { + _methodReferenceAnalyzer.WalkMethod(method.Method, method.KlassInst, method.MethodInst); + } + Debug.Log($"iteration:[{i}] genericClass:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + } + + private bool IsNotShareableAOTGenericType(TypeDef typeDef) + { + if (!IsAotType(typeDef)) + { + return false; + } + return typeDef.GenericParameters.Any(c => !c.HasReferenceTypeConstraint); + } + + private bool IsNotShareableAOTGenericMethod(MethodDef method) + { + if (!IsAotGenericMethod(method)) + { + return false; + } + return method.GenericParameters.Concat(method.DeclaringType.GenericParameters).Any(c => !c.HasReferenceTypeConstraint); + } + + private void FilterAOTGenericTypeAndMethods() + { + ConstraintContext cc = this.ConstraintContext; + AotGenericTypes.AddRange(_genericTypes.Where(type => IsNotShareableAOTGenericType(type.Type)).Select(gc => cc.ApplyConstraints(gc))); + AotGenericMethods.AddRange(_genericMethods.Where(method => IsNotShareableAOTGenericMethod(method.Method)).Select(gm => cc.ApplyConstraints(gm))); + } + + public void Run() + { + Prepare(); + RecursiveCollect(); + FilterAOTGenericTypeAndMethods(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta new file mode 100644 index 00000000..2e573fc5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30bbf4a80a6cf3a43b3f489747d9dd6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs new file mode 100644 index 00000000..4a9cd3c4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs @@ -0,0 +1,73 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.AOT +{ + + public class ConstraintContext + { + public class ImplType + { + public TypeSig BaseType { get; } + + public List Interfaces { get; } + + public bool ValueType { get; } + + private readonly int _hash; + + public ImplType(TypeSig baseType, List interfaces, bool valueType) + { + BaseType = baseType; + Interfaces = interfaces; + ValueType = valueType; + _hash = ComputHash(); + } + + public override bool Equals(object obj) + { + ImplType o = (ImplType)obj; + return MetaUtil.EqualsTypeSig(this.BaseType, o.BaseType) + && MetaUtil.EqualsTypeSigArray(this.Interfaces, o.Interfaces) + && this.ValueType == o.ValueType; + } + + public override int GetHashCode() + { + return _hash; + } + + private int ComputHash() + { + int hash = 0; + if (BaseType != null) + { + hash = HashUtil.CombineHash(hash, TypeEqualityComparer.Instance.GetHashCode(BaseType)); + } + if (Interfaces.Count > 0) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(Interfaces)); + } + + return hash; + } + } + + public HashSet ImplTypes { get; } = new HashSet(); + + public GenericClass ApplyConstraints(GenericClass gc) + { + return gc; + } + + public GenericMethod ApplyConstraints(GenericMethod gm) + { + return gm; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta new file mode 100644 index 00000000..3e99cab9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 812d81a75b690394bbe16ef5f0bcbc46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs new file mode 100644 index 00000000..1b6021ff --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs @@ -0,0 +1,139 @@ +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.AOT +{ + public class GenericReferenceWriter + { + private static readonly Dictionary _typeNameMapping = new Dictionary + { + {typeof(bool), "bool" }, + {typeof(byte), "byte" }, + {typeof(sbyte), "sbyte" }, + {typeof(short), "short" }, + {typeof(ushort), "ushort" }, + {typeof(int), "int" }, + {typeof(uint), "uint" }, + {typeof(long), "long" }, + {typeof(ulong), "ulong" }, + {typeof(float), "float" }, + {typeof(double), "double" }, + {typeof(object), "object" }, + {typeof(string), "string" }, + }; + + private readonly Dictionary _typeSimpleNameMapping = new Dictionary(); + private readonly Regex _systemTypePattern; + private readonly Regex _genericPattern = new Regex(@"`\d+"); + + public GenericReferenceWriter() + { + foreach (var e in _typeNameMapping) + { + _typeSimpleNameMapping.Add(e.Key.FullName, e.Value); + } + _systemTypePattern = new Regex(string.Join("|", _typeSimpleNameMapping.Keys.Select (k => $@"\b{Regex.Escape(k)}\b"))); + } + + public string PrettifyTypeSig(string typeSig) + { + string s = _genericPattern.Replace(typeSig, "").Replace('/', '.'); + return _systemTypePattern.Replace(s, m => _typeSimpleNameMapping[m.Groups[0].Value]); + } + + public string PrettifyMethodSig(string methodSig) + { + string s = PrettifyTypeSig(methodSig).Replace("::", "."); + if (s.Contains(".ctor(")) + { + s = "new " + s.Replace(".ctor(", "("); + } + return s; + } + + public void Write(List types, List methods, string outputFile) + { + string parentDir = Directory.GetParent(outputFile).FullName; + Directory.CreateDirectory(parentDir); + + List codes = new List(); + codes.Add("using System.Collections.Generic;"); + codes.Add("public class AOTGenericReferences : UnityEngine.MonoBehaviour"); + codes.Add("{"); + + codes.Add(""); + codes.Add("\t// {{ AOT assemblies"); + codes.Add("\tpublic static readonly IReadOnlyList PatchedAOTAssemblyList = new List"); + codes.Add("\t{"); + List modules = new HashSet( + types.Select(t => t.Type.Module).Concat(methods.Select(m => m.Method.Module))).ToList(); + modules.Sort((a, b) => a.Name.CompareTo(b.Name)); + foreach (dnlib.DotNet.ModuleDef module in modules) + { + codes.Add($"\t\t\"{module.Name}\","); + } + codes.Add("\t};"); + codes.Add("\t// }}"); + + + codes.Add(""); + codes.Add("\t// {{ constraint implement type"); + + codes.Add("\t// }} "); + + codes.Add(""); + codes.Add("\t// {{ AOT generic types"); + + List typeNames = types.Select(t => PrettifyTypeSig(t.ToTypeSig().ToString())).ToList(); + typeNames.Sort(string.CompareOrdinal); + foreach(var typeName in typeNames) + { + codes.Add($"\t// {typeName}"); + } + + codes.Add("\t// }}"); + + codes.Add(""); + codes.Add("\tpublic void RefMethods()"); + codes.Add("\t{"); + + List<(string, string, string)> methodTypeAndNames = methods.Select(m => + (PrettifyTypeSig(m.Method.DeclaringType.ToString()), PrettifyMethodSig(m.Method.Name), PrettifyMethodSig(m.ToMethodSpec().ToString()))) + .ToList(); + methodTypeAndNames.Sort((a, b) => + { + int c = String.Compare(a.Item1, b.Item1, StringComparison.Ordinal); + if (c != 0) + { + return c; + } + + c = String.Compare(a.Item2, b.Item2, StringComparison.Ordinal); + if (c != 0) + { + return c; + } + return String.Compare(a.Item3, b.Item3, StringComparison.Ordinal); + }); + foreach(var method in methodTypeAndNames) + { + codes.Add($"\t\t// {PrettifyMethodSig(method.Item3)}"); + } + codes.Add("\t}"); + + codes.Add("}"); + + + var utf8WithoutBom = new System.Text.UTF8Encoding(false); + File.WriteAllText(outputFile, string.Join("\n", codes), utf8WithoutBom); + Debug.Log($"[GenericReferenceWriter] write {outputFile}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta new file mode 100644 index 00000000..9fc14972 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1243cf04685361478972f93b5ca868a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta new file mode 100644 index 00000000..428a0a12 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f80d2287f01c89642a74b0a60f7a3305 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs new file mode 100644 index 00000000..b879afff --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs @@ -0,0 +1,258 @@ +using System; +using HybridCLR.Editor.Installer; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using System.Reflection; +using HybridCLR.Editor.Settings; +#if UNITY_2019 && (UNITY_IOS || UNITY_TVOS) +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEditor.iOS.Xcode; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2019 + { + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (!HybridCLRSettings.Instance.enable) + return; + /* + * 1. 生成lump,并且添加到工程 + 3. 将libil2cpp目录复制到 Library/. 删除旧的. search paths里修改 libil2cpp/include为libil2cpp + 3. Libraries/bdwgc/include -> Libraries/external/bdwgc/include + 4. 将external目录复制到 Library/external。删除旧目录 + 5. 将Library/external/baselib/Platforms/OSX改名为 IOS 全大写 + 6. 将 external/zlib下c 文件添加到工程 + 7. 移除libil2cpp.a + 8. Include path add libil2cpp/os/ClassLibraryPAL/brotli/include + 9. add external/xxHash + 10. add "#include " to Classes/Prefix.pch + */ + + string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject); + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp"; + string lumpDir = $"{pathToBuiltProject}/Libraries/lumps"; + string srcExternalDir = $"{SettingsUtil.LocalIl2CppDir}/external"; + string dstExternalDir = $"{pathToBuiltProject}/Libraries/external"; + //RemoveExternalLibil2cppOption(srcExternalDir, dstExternalDir); + CopyLibil2cppToXcodeProj(srcLibil2cppDir, dstLibil2cppDir); + CopyExternalToXcodeProj(srcExternalDir, dstExternalDir); + var lumpFiles = CreateLumps(dstLibil2cppDir, lumpDir); + var extraSources = GetExtraSourceFiles(dstExternalDir, dstLibil2cppDir); + var cflags = new List() + { + "-DIL2CPP_MONO_DEBUGGER_DISABLED", + }; + ModifyPBXProject(pathToBuiltProject, pbxprojFile, lumpFiles, extraSources, cflags); + AddSystemHeaderToPrefixPch(pathToBuiltProject); + } + + private static void AddSystemHeaderToPrefixPch(string pathToBuiltProject) + { + // 如果不将 stdio.h 添加到 Prefix.pch, zutil.c会有编译错误 + string prefixPchFile = $"{pathToBuiltProject}/Classes/Prefix.pch"; + string fileContent = File.ReadAllText(prefixPchFile, Encoding.UTF8); + if (!fileContent.Contains("stdio.h")) + { + string newFileContent = fileContent + "\n#include \n"; + File.WriteAllText(prefixPchFile, newFileContent, Encoding.UTF8); + UnityEngine.Debug.Log($"append header to {prefixPchFile}"); + } + } + + private static string GetRelativePathFromProj(string path) + { + return path.Substring(path.IndexOf("Libraries", StringComparison.Ordinal)).Replace('\\', '/'); + } + + private static void ModifyPBXProject(string pathToBuiltProject, string pbxprojFile, List lumpFiles, List extraFiles, List cflags) + { + var proj = new PBXProject(); + proj.ReadFromFile(pbxprojFile); + string targetGUID = proj.GetUnityFrameworkTargetGuid(); + // 移除旧的libil2cpp.a + var libil2cppGUID = proj.FindFileGuidByProjectPath("Libraries/libil2cpp.a"); + if (!string.IsNullOrEmpty(libil2cppGUID)) + { + proj.RemoveFileFromBuild(targetGUID, libil2cppGUID); + proj.RemoveFile(libil2cppGUID); + File.Delete(Path.Combine(pathToBuiltProject, "Libraries", "libil2cpp.a")); + } + + //var lumpGroupGuid = proj.AddFile("Lumps", $"Classes/Lumps", PBXSourceTree.Group); + + foreach (var lumpFile in lumpFiles) + { + string lumpFileName = Path.GetFileName(lumpFile.lumpFile); + string projPathOfFile = $"Classes/Lumps/{lumpFileName}"; + string relativePathOfFile = GetRelativePathFromProj(lumpFile.lumpFile); + string lumpGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(lumpGuid)) + { + proj.RemoveFileFromBuild(targetGUID, lumpGuid); + proj.RemoveFile(lumpGuid); + } + lumpGuid = proj.AddFile(relativePathOfFile, projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach (var extraFile in extraFiles) + { + string projPathOfFile = $"Classes/Extrals/{Path.GetFileName(extraFile)}"; + string extraFileGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(extraFileGuid)) + { + proj.RemoveFileFromBuild(targetGUID, extraFileGuid); + proj.RemoveFile(extraFileGuid); + //Debug.LogWarning($"remove exist extra file:{projPathOfFile} guid:{extraFileGuid}"); + } + var lumpGuid = proj.AddFile(GetRelativePathFromProj(extraFile), projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach(var configName in proj.BuildConfigNames()) + { + //Debug.Log($"build config:{bcn}"); + string configGuid = proj.BuildConfigByName(targetGUID, configName); + string headerSearchPaths = "HEADER_SEARCH_PATHS"; + string hspProp = proj.GetBuildPropertyForConfig(configGuid, headerSearchPaths); + //Debug.Log($"config guid:{configGuid} prop:{hspProp}"); + string newPro = hspProp.Replace("libil2cpp/include", "libil2cpp") + .Replace("Libraries/bdwgc", "Libraries/external/bdwgc"); + + //if (!newPro.Contains("Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include")) + //{ + // newPro += " $(SRCROOT)/Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include"; + //} + if (!newPro.Contains("Libraries/external/xxHash")) + { + newPro += " $(SRCROOT)/Libraries/external/xxHash"; + } + newPro += " $(SRCR00T)/Libraries/external/mono"; + //Debug.Log($"config:{bcn} new prop:{newPro}"); + proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro); + + string cflagKey = "OTHER_CFLAGS"; + string cfProp = proj.GetBuildPropertyForConfig(configGuid, cflagKey); + foreach (var flag in cflags) + { + if (!cfProp.Contains(flag)) + { + cfProp += " " + flag; + } + } + if (configName.Contains("Debug") && !cfProp.Contains("-DIL2CPP_DEBUG=")) + { + cfProp += " -DIL2CPP_DEBUG=1 -DDEBUG=1"; + } + proj.SetBuildPropertyForConfig(configGuid, cflagKey, cfProp); + + } + proj.WriteToFile(pbxprojFile); + } + + private static void CopyLibil2cppToXcodeProj(string srcLibil2cppDir, string dstLibil2cppDir) + { + BashUtil.RemoveDir(dstLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, dstLibil2cppDir, true); + } + + + private static void CopyExternalToXcodeProj(string srcExternalDir, string dstExternalDir) + { + BashUtil.RemoveDir(dstExternalDir); + BashUtil.CopyDir(srcExternalDir, dstExternalDir, true); + + //string baselibPlatfromsDir = $"{dstExternalDir}/baselib/Platforms"; + //BashUtil.RemoveDir($"{baselibPlatfromsDir}/IOS"); + //BashUtil.CopyDir($"{baselibPlatfromsDir}/OSX", $"{baselibPlatfromsDir}/IOS", true); + } + + class LumpFile + { + public List cppFiles = new List(); + + public readonly string lumpFile; + + public readonly string il2cppConfigFile; + + public LumpFile(string lumpFile, string il2cppConfigFile) + { + this.lumpFile = lumpFile; + this.il2cppConfigFile = il2cppConfigFile; + this.cppFiles.Add(il2cppConfigFile); + } + + public void SaveFile() + { + var lumpFileContent = new List(); + foreach (var file in cppFiles) + { + lumpFileContent.Add($"#include \"{GetRelativePathFromProj(file)}\""); + } + File.WriteAllLines(lumpFile, lumpFileContent, Encoding.UTF8); + Debug.Log($"create lump file:{lumpFile}"); + } + } + + private static List CreateLumps(string libil2cppDir, string outputDir) + { + BashUtil.RecreateDir(outputDir); + + string il2cppConfigFile = $"{libil2cppDir}/il2cpp-config.h"; + var lumpFiles = new List(); + int lumpFileIndex = 0; + foreach (var cppDir in Directory.GetDirectories(libil2cppDir, "*", SearchOption.AllDirectories).Concat(new string[] {libil2cppDir})) + { + var lumpFile = new LumpFile($"{outputDir}/lump_{Path.GetFileName(cppDir)}_{lumpFileIndex}.cpp", il2cppConfigFile); + foreach (var file in Directory.GetFiles(cppDir, "*.cpp", SearchOption.TopDirectoryOnly)) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + ++lumpFileIndex; + } + + var mmFiles = Directory.GetFiles(libil2cppDir, "*.mm", SearchOption.AllDirectories); + if (mmFiles.Length > 0) + { + var lumpFile = new LumpFile($"{outputDir}/lump_mm.mm", il2cppConfigFile); + foreach (var file in mmFiles) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + } + return lumpFiles; + } + + private static List GetExtraSourceFiles(string externalDir, string libil2cppDir) + { + var files = new List(); + foreach (string extraDir in new string[] + { + $"{externalDir}/zlib", + $"{externalDir}/xxHash", + $"{libil2cppDir}/os/ClassLibraryPAL/brotli", + }) + { + if (!Directory.Exists(extraDir)) + { + continue; + } + files.AddRange(Directory.GetFiles(extraDir, "*.c", SearchOption.AllDirectories)); + } + return files; + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs.meta new file mode 100644 index 00000000..64df0932 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2019.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2f62ca12f2eb4f2fba8e9cb51279421 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs new file mode 100644 index 00000000..a7ebc3ad --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs @@ -0,0 +1,244 @@ +using System; +using HybridCLR.Editor.Installer; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using System.Reflection; +using HybridCLR.Editor.Settings; +#if (UNITY_2020 || UNITY_2021) && (UNITY_IOS || UNITY_TVOS) +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEditor.iOS.Xcode; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2020Or2021 + { + + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (!HybridCLRSettings.Instance.enable) + return; + /* + * 1. 生成lump,并且添加到工程 + 3. 将libil2cpp目录复制到 Library/. 删除旧的. search paths里修改 libil2cpp/include为libil2cpp + 3. Libraries/bdwgc/include -> Libraries/external/bdwgc/include + 4. 将external目录复制到 Library/external。删除旧目录 + 5. 将Library/external/baselib/Platforms/OSX改名为 IOS 全大写 + 6. 将 external/zlib下c 文件添加到工程 + 7. 移除libil2cpp.a + 8. Include path add libil2cpp/os/ClassLibraryPAL/brotli/include + 9. add external/xxHash + */ + + string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject); + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp"; + string lumpDir = $"{pathToBuiltProject}/Libraries/lumps"; + string srcExternalDir = $"{SettingsUtil.LocalIl2CppDir}/external"; + string dstExternalDir = $"{pathToBuiltProject}/Libraries/external"; + //RemoveExternalLibil2cppOption(srcExternalDir, dstExternalDir); + CopyLibil2cppToXcodeProj(srcLibil2cppDir, dstLibil2cppDir); + CopyExternalToXcodeProj(srcExternalDir, dstExternalDir); + var lumpFiles = CreateLumps(dstLibil2cppDir, lumpDir); + var extraSources = GetExtraSourceFiles(dstExternalDir, dstLibil2cppDir); + var cflags = new List() + { + "-DIL2CPP_MONO_DEBUGGER_DISABLED", + }; + ModifyPBXProject(pathToBuiltProject, pbxprojFile, lumpFiles, extraSources, cflags); + } + + private static string GetRelativePathFromProj(string path) + { + return path.Substring(path.IndexOf("Libraries", StringComparison.Ordinal)).Replace('\\', '/'); + } + + private static void ModifyPBXProject(string pathToBuiltProject, string pbxprojFile, List lumpFiles, List extraFiles, List cflags) + { + var proj = new PBXProject(); + proj.ReadFromFile(pbxprojFile); + string targetGUID = proj.GetUnityFrameworkTargetGuid(); + // 移除旧的libil2cpp.a + var libil2cppGUID = proj.FindFileGuidByProjectPath("Libraries/libil2cpp.a"); + if (!string.IsNullOrEmpty(libil2cppGUID)) + { + proj.RemoveFileFromBuild(targetGUID, libil2cppGUID); + proj.RemoveFile(libil2cppGUID); + File.Delete(Path.Combine(pathToBuiltProject, "Libraries", "libil2cpp.a")); + } + + //var lumpGroupGuid = proj.AddFile("Lumps", $"Classes/Lumps", PBXSourceTree.Group); + + foreach (var lumpFile in lumpFiles) + { + string lumpFileName = Path.GetFileName(lumpFile.lumpFile); + string projPathOfFile = $"Classes/Lumps/{lumpFileName}"; + string relativePathOfFile = GetRelativePathFromProj(lumpFile.lumpFile); + string lumpGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(lumpGuid)) + { + proj.RemoveFileFromBuild(targetGUID, lumpGuid); + proj.RemoveFile(lumpGuid); + } + lumpGuid = proj.AddFile(relativePathOfFile, projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach (var extraFile in extraFiles) + { + string projPathOfFile = $"Classes/Extrals/{Path.GetFileName(extraFile)}"; + string extraFileGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(extraFileGuid)) + { + proj.RemoveFileFromBuild(targetGUID, extraFileGuid); + proj.RemoveFile(extraFileGuid); + //Debug.LogWarning($"remove exist extra file:{projPathOfFile} guid:{extraFileGuid}"); + } + var lumpGuid = proj.AddFile(GetRelativePathFromProj(extraFile), projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach(var configName in proj.BuildConfigNames()) + { + //Debug.Log($"build config:{bcn}"); + string configGuid = proj.BuildConfigByName(targetGUID, configName); + string headerSearchPaths = "HEADER_SEARCH_PATHS"; + string hspProp = proj.GetBuildPropertyForConfig(configGuid, headerSearchPaths); + //Debug.Log($"config guid:{configGuid} prop:{hspProp}"); + string newPro = hspProp.Replace("libil2cpp/include", "libil2cpp") + .Replace("Libraries/bdwgc", "Libraries/external/bdwgc"); + + if (!newPro.Contains("Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include")) + { + newPro += " $(SRCROOT)/Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include"; + } + if (!newPro.Contains("Libraries/external/xxHash")) + { + newPro += " $(SRCROOT)/Libraries/external/xxHash"; + } + newPro += " $(SRCR00T)/Libraries/external/mono"; + //Debug.Log($"config:{bcn} new prop:{newPro}"); + proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro); + + string cflagKey = "OTHER_CFLAGS"; + string cfProp = proj.GetBuildPropertyForConfig(configGuid, cflagKey); + foreach (var flag in cflags) + { + if (!cfProp.Contains(flag)) + { + cfProp += " " + flag; + } + } + if (configName.Contains("Debug") && !cfProp.Contains("-DIL2CPP_DEBUG=")) + { + cfProp += " -DIL2CPP_DEBUG=1 -DDEBUG=1"; + } + proj.SetBuildPropertyForConfig(configGuid, cflagKey, cfProp); + + } + proj.WriteToFile(pbxprojFile); + } + + private static void CopyLibil2cppToXcodeProj(string srcLibil2cppDir, string dstLibil2cppDir) + { + BashUtil.RemoveDir(dstLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, dstLibil2cppDir, true); + } + + + private static void CopyExternalToXcodeProj(string srcExternalDir, string dstExternalDir) + { + BashUtil.RemoveDir(dstExternalDir); + BashUtil.CopyDir(srcExternalDir, dstExternalDir, true); + + string baselibPlatfromsDir = $"{dstExternalDir}/baselib/Platforms"; + BashUtil.RemoveDir($"{baselibPlatfromsDir}/IOS"); + BashUtil.CopyDir($"{baselibPlatfromsDir}/OSX", $"{baselibPlatfromsDir}/IOS", true); + } + + class LumpFile + { + public List cppFiles = new List(); + + public readonly string lumpFile; + + public readonly string il2cppConfigFile; + + public LumpFile(string lumpFile, string il2cppConfigFile) + { + this.lumpFile = lumpFile; + this.il2cppConfigFile = il2cppConfigFile; + this.cppFiles.Add(il2cppConfigFile); + } + + public void SaveFile() + { + var lumpFileContent = new List(); + foreach (var file in cppFiles) + { + lumpFileContent.Add($"#include \"{GetRelativePathFromProj(file)}\""); + } + File.WriteAllLines(lumpFile, lumpFileContent, Encoding.UTF8); + Debug.Log($"create lump file:{lumpFile}"); + } + } + + private static List CreateLumps(string libil2cppDir, string outputDir) + { + BashUtil.RecreateDir(outputDir); + + string il2cppConfigFile = $"{libil2cppDir}/il2cpp-config.h"; + var lumpFiles = new List(); + int lumpFileIndex = 0; + foreach (var cppDir in Directory.GetDirectories(libil2cppDir, "*", SearchOption.AllDirectories).Concat(new string[] {libil2cppDir})) + { + var lumpFile = new LumpFile($"{outputDir}/lump_{Path.GetFileName(cppDir)}_{lumpFileIndex}.cpp", il2cppConfigFile); + foreach (var file in Directory.GetFiles(cppDir, "*.cpp", SearchOption.TopDirectoryOnly)) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + ++lumpFileIndex; + } + + var mmFiles = Directory.GetFiles(libil2cppDir, "*.mm", SearchOption.AllDirectories); + if (mmFiles.Length > 0) + { + var lumpFile = new LumpFile($"{outputDir}/lump_mm.mm", il2cppConfigFile); + foreach (var file in mmFiles) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + } + return lumpFiles; + } + + private static List GetExtraSourceFiles(string externalDir, string libil2cppDir) + { + var files = new List(); + foreach (string extraDir in new string[] + { + $"{externalDir}/zlib", + $"{externalDir}/xxHash", + $"{libil2cppDir}/os/ClassLibraryPAL/brotli", + }) + { + if (!Directory.Exists(extraDir)) + { + continue; + } + files.AddRange(Directory.GetFiles(extraDir, "*.c", SearchOption.AllDirectories)); + } + return files; + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs.meta new file mode 100644 index 00000000..7eba538e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2020Or2021.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61948fcb1bc40ba47b8c10b0ae801ebb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs new file mode 100644 index 00000000..084e3882 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs @@ -0,0 +1,82 @@ +using HybridCLR.Editor.Installer; +using HybridCLR.Editor.Settings; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEngine; + +#if UNITY_2022 && (UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS) + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer + { + + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (!HybridCLRSettings.Instance.enable) + return; + string pbxprojFile = BuildProcessorUtil.GetXcodeProjectFile(pathToBuiltProject); + RemoveExternalLibil2cppOption(pbxprojFile); + CopyLibil2cppToXcodeProj(pathToBuiltProject); + } + + private static string TryRemoveDunplicateShellScriptSegment(string pbxprojFile, string pbxprojContent) + { + // will appear duplicated Shell Script segment when append to existed xcode project. + // This is unity bug. + // we remove duplicated Shell Script to avoid build error. + string copyFileComment = @"/\* CopyFiles \*/,\s+([A-Z0-9]{24}) /\* ShellScript \*/,\s+([A-Z0-9]{24}) /\* ShellScript \*/,"; + var m = Regex.Match(pbxprojContent, copyFileComment, RegexOptions.Multiline); + if (!m.Success) + { + return pbxprojContent; + } + + if (m.Groups[1].Value != m.Groups[2].Value) + { + throw new BuildFailedException($"find invalid /* ShellScript */ segment"); + } + + int startIndexOfDupShellScript = m.Groups[2].Index; + int endIndexOfDupShellScript = pbxprojContent.IndexOf(",", startIndexOfDupShellScript); + + pbxprojContent = pbxprojContent.Remove(startIndexOfDupShellScript, endIndexOfDupShellScript + 1 - startIndexOfDupShellScript); + Debug.LogWarning($"[AddLil2cppSourceCodeToXcodeproj] remove duplicated '/* ShellScript */' from file '{pbxprojFile}'"); + return pbxprojContent; + } + + private static void RemoveExternalLibil2cppOption(string pbxprojFile) + { + string pbxprojContent = File.ReadAllText(pbxprojFile, Encoding.UTF8); + string removeBuildOption = @"--external-lib-il2-cpp=\""$PROJECT_DIR/Libraries/libil2cpp.a\"""; + if (pbxprojContent.Contains(removeBuildOption)) + { + pbxprojContent = pbxprojContent.Replace(removeBuildOption, ""); + Debug.Log($"[AddLil2cppSourceCodeToXcodeproj] remove il2cpp build option '{removeBuildOption}' from file '{pbxprojFile}'"); + } + else + { + Debug.LogWarning($"[AddLil2cppSourceCodeToXcodeproj] project.pbxproj remove building option:'{removeBuildOption}' fail. This may occur when 'Append' to existing xcode project in building"); + } + + pbxprojContent = TryRemoveDunplicateShellScriptSegment(pbxprojFile, pbxprojContent); + + + File.WriteAllText(pbxprojFile, pbxprojContent, Encoding.UTF8); + } + + private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject) + { + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string destLibil2cppDir = $"{pathToBuiltProject}/Il2CppOutputProject/IL2CPP/libil2cpp"; + BashUtil.RemoveDir(destLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, destLibil2cppDir, true); + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta new file mode 100644 index 00000000..e71a7652 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4ce072f7e4a17248a3d9ebfd011356b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs new file mode 100644 index 00000000..a3c880c0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs @@ -0,0 +1,34 @@ +using HybridCLR.Editor.Installer; +using HybridCLR.Editor.Settings; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEngine; + +#if UNITY_2023_1_OR_NEWER && (UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS) + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer + { + + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (!HybridCLRSettings.Instance.enable) + return; + CopyLibil2cppToXcodeProj(pathToBuiltProject); + } + + private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject) + { + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string destLibil2cppDir = $"{pathToBuiltProject}/Il2CppOutputProject/IL2CPP/libil2cpp"; + BashUtil.RemoveDir(destLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, destLibil2cppDir, true); + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs.meta new file mode 100644 index 00000000..af06bb04 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2023OrNewer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2fa46135129b046a28014d58fdfd18ca \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs new file mode 100644 index 00000000..7bb4f774 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using UnityEditor.Build; + +namespace HybridCLR.Editor.BuildProcessors +{ + + public static class BuildProcessorUtil + { + + public static string GetXcodeProjectFile(string pathToBuiltProject) + { + foreach (string dir in Directory.GetDirectories(pathToBuiltProject, "*.xcodeproj", SearchOption.TopDirectoryOnly)) + { + string pbxprojFile = $"{dir}/project.pbxproj"; + if (File.Exists(pbxprojFile)) + { + return pbxprojFile; + } + } + throw new BuildFailedException($"can't find xxxx.xcodeproj/project.pbxproj in {pathToBuiltProject}"); + } + } +} + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs.meta new file mode 100644 index 00000000..6cd79d21 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/BuildProcessorUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c680e56f90f2745298a90803c04f6efc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs new file mode 100644 index 00000000..994683b4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs @@ -0,0 +1,94 @@ +using HybridCLR.Editor.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using static UnityEngine.GraphicsBuffer; + +namespace HybridCLR.Editor.BuildProcessors +{ + internal class CheckSettings : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public static bool DisableMethodBridgeDevelopmentFlagChecking { get; set; } + + public void OnPreprocessBuild(BuildReport report) + { + HybridCLRSettings globalSettings = SettingsUtil.HybridCLRSettings; + if (!globalSettings.enable || globalSettings.useGlobalIl2cpp) + { + string oldIl2cppPath = Environment.GetEnvironmentVariable("UNITY_IL2CPP_PATH"); + if (!string.IsNullOrEmpty(oldIl2cppPath)) + { + Environment.SetEnvironmentVariable("UNITY_IL2CPP_PATH", ""); + Debug.Log($"[CheckSettings] clean process environment variable: UNITY_IL2CPP_PATH, old vlaue:'{oldIl2cppPath}'"); + } + } + else + { + string curIl2cppPath = Environment.GetEnvironmentVariable("UNITY_IL2CPP_PATH"); + if (curIl2cppPath != SettingsUtil.LocalIl2CppDir) + { + Environment.SetEnvironmentVariable("UNITY_IL2CPP_PATH", SettingsUtil.LocalIl2CppDir); + Debug.Log($"[CheckSettings] UNITY_IL2CPP_PATH old value:'{curIl2cppPath}', new value:'{SettingsUtil.LocalIl2CppDir}'"); + } + } + if (!globalSettings.enable) + { + return; + } + BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); + ScriptingImplementation curScriptingImplementation = PlayerSettings.GetScriptingBackend(buildTargetGroup); + ScriptingImplementation targetScriptingImplementation = ScriptingImplementation.IL2CPP; + if (curScriptingImplementation != targetScriptingImplementation) + { + Debug.LogError($"[CheckSettings] current ScriptingBackend:{curScriptingImplementation},have been switched to:{targetScriptingImplementation} automatically"); + PlayerSettings.SetScriptingBackend(buildTargetGroup, targetScriptingImplementation); + } + + var installer = new Installer.InstallerController(); + if (!installer.HasInstalledHybridCLR()) + { + throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'"); + } + + if (installer.PackageVersion != installer.InstalledLibil2cppVersion) + { + throw new BuildFailedException($"You must run `HybridCLR/Installer` after upgrading package"); + } + + HybridCLRSettings gs = SettingsUtil.HybridCLRSettings; + if (((gs.hotUpdateAssemblies?.Length + gs.hotUpdateAssemblyDefinitions?.Length) ?? 0) == 0) + { + Debug.LogWarning("[CheckSettings] No hot update modules configured in HybridCLRSettings"); + } + + if (!DisableMethodBridgeDevelopmentFlagChecking) + { + string methodBridgeFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp"; + var match = Regex.Match(File.ReadAllText(methodBridgeFile), @"// DEVELOPMENT=(\d)"); + if (match.Success) + { + int developmentFlagInMethodBridge = int.Parse(match.Groups[1].Value); + int developmentFlagInEditorSettings = EditorUserBuildSettings.development ? 1 : 0; + if (developmentFlagInMethodBridge != developmentFlagInEditorSettings) + { + Debug.LogError($"[CheckSettings] MethodBridge.cpp DEVELOPMENT flag:{developmentFlagInMethodBridge} is inconsistent with EditorUserBuildSettings.development:{developmentFlagInEditorSettings}. Please run 'HybridCLR/Generate/All' before building."); + } + } + else + { + Debug.LogError("[CheckSettings] MethodBridge.cpp DEVELOPMENT flag not found. Please run 'HybridCLR/Generate/All' before building."); + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta new file mode 100644 index 00000000..0cc36431 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb4ba063068b17247b2d0233420aa5f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs new file mode 100644 index 00000000..96303167 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs @@ -0,0 +1,134 @@ +using HybridCLR.Editor.Installer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.UnityLinker; +using UnityEngine; +#if !UNITY_2021_1_OR_NEWER +using UnityEditor.Il2Cpp; +#endif + +namespace HybridCLR.Editor.BuildProcessors +{ + internal class CopyStrippedAOTAssemblies : IPostprocessBuildWithReport, IPreprocessBuildWithReport +#if !UNITY_2021_1_OR_NEWER + , IIl2CppProcessor +#endif + { + + public int callbackOrder => 0; + +#if UNITY_2021_1_OR_NEWER + public static string GetStripAssembliesDir2021(BuildTarget target) + { + string projectDir = SettingsUtil.ProjectDir; + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped"; + case BuildTarget.StandaloneLinux64: + return $"{projectDir}/Library/Bee/artifacts/LinuxPlayerBuildProgram/ManagedStripped"; + case BuildTarget.WSAPlayer: + return $"{projectDir}/Library/Bee/artifacts/UWPPlayerBuildProgram/ManagedStripped"; + case BuildTarget.Android: + return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped"; +#if TUANJIE_2022_3_OR_NEWER + case BuildTarget.HMIAndroid: + return $"{projectDir}/Library/Bee/artifacts/HMIAndroid/ManagedStripped"; +#endif + case BuildTarget.iOS: +#if UNITY_TVOS + case BuildTarget.tvOS: +#endif + return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped"; +#if UNITY_VISIONOS + case BuildTarget.VisionOS: +#if UNITY_6000_0_OR_NEWER + return $"{projectDir}/Library/Bee/artifacts/VisionOS/ManagedStripped"; +#else + return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped"; +#endif +#endif + case BuildTarget.WebGL: + return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped"; + case BuildTarget.StandaloneOSX: + return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped"; + case BuildTarget.PS4: + return $"{projectDir}/Library/Bee/artifacts/PS4PlayerBuildProgram/ManagedStripped"; + case BuildTarget.PS5: + return $"{projectDir}/Library/Bee/artifacts/PS5PlayerBuildProgram/ManagedStripped"; +#if UNITY_WEIXINMINIGAME + case BuildTarget.WeixinMiniGame: + return $"{projectDir}/Library/Bee/artifacts/WeixinMiniGame/ManagedStripped"; +#endif +#if UNITY_OPENHARMONY + case BuildTarget.OpenHarmony: + return $"{projectDir}/Library/Bee/artifacts/OpenHarmonyPlayerBuildProgram/ManagedStripped"; +#endif + default: return ""; + } + } +#else + private string GetStripAssembliesDir2020(BuildTarget target) + { + string subPath = target == BuildTarget.Android ? + "assets/bin/Data/Managed" : + "Data/Managed/"; + return $"{SettingsUtil.ProjectDir}/Temp/StagingArea/{subPath}"; + } + + public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data) + { + BuildTarget target = report.summary.platform; + CopyStripDlls(GetStripAssembliesDir2020(target), target); + } +#endif + + public static void CopyStripDlls(string srcStripDllPath, BuildTarget target) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[CopyStrippedAOTAssemblies] disabled"); + return; + } + Debug.Log($"[CopyStrippedAOTAssemblies] CopyScripDlls. src:{srcStripDllPath} target:{target}"); + + var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + + Directory.CreateDirectory(dstPath); + + foreach (var fileFullPath in Directory.GetFiles(srcStripDllPath, "*.dll")) + { + var file = Path.GetFileName(fileFullPath); + Debug.Log($"[CopyStrippedAOTAssemblies] copy strip dll {fileFullPath} ==> {dstPath}/{file}"); + File.Copy($"{fileFullPath}", $"{dstPath}/{file}", true); + } + } + + public void OnPostprocessBuild(BuildReport report) + { +#if UNITY_2021_1_OR_NEWER + BuildTarget target = report.summary.platform; + string srcStripDllPath = GetStripAssembliesDir2021(target); + if (!string.IsNullOrEmpty(srcStripDllPath) && Directory.Exists(srcStripDllPath)) + { + CopyStripDlls(srcStripDllPath, target); + } +#endif + } + + public void OnPreprocessBuild(BuildReport report) + { + BuildTarget target = report.summary.platform; + var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + BashUtil.RecreateDir(dstPath); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta new file mode 100644 index 00000000..3ebc0e10 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7884710ec2f8e545b3fe9aa05def5a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs new file mode 100644 index 00000000..1cb56b64 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs @@ -0,0 +1,68 @@ +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + /// + /// 将热更新dll从Build过程中过滤,防止打包到主工程中 + /// + internal class FilterHotFixAssemblies : IFilterBuildAssemblies + { + public int callbackOrder => 0; + + public string[] OnFilterAssemblies(BuildOptions buildOptions, string[] assemblies) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[FilterHotFixAssemblies] disabled"); + return assemblies; + } + List allHotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + // 检查是否重复填写 + var hotUpdateDllSet = new HashSet(); + foreach(var hotUpdateDll in allHotUpdateDllNames) + { + if (string.IsNullOrWhiteSpace(hotUpdateDll)) + { + throw new BuildFailedException($"hot update assembly name cann't be empty"); + } + if (!hotUpdateDllSet.Add(hotUpdateDll)) + { + throw new BuildFailedException($"hot update assembly:{hotUpdateDll} is duplicated"); + } + } + + var assResolver = MetaUtil.CreateHotUpdateAssemblyResolver(EditorUserBuildSettings.activeBuildTarget, allHotUpdateDllNames); + // 检查是否填写了正确的dll名称 + foreach (var hotUpdateDllName in allHotUpdateDllNames) + { + if (assemblies.Select(Path.GetFileNameWithoutExtension).All(ass => ass != hotUpdateDllName) + && string.IsNullOrEmpty(assResolver.ResolveAssembly(hotUpdateDllName, false))) + { + throw new BuildFailedException($"hot update assembly:{hotUpdateDllName} doesn't exist"); + } + } + + // 将热更dll从打包列表中移除 + return assemblies.Where(ass => + { + string assName = Path.GetFileNameWithoutExtension(ass); + bool reserved = allHotUpdateDllNames.All(dll => !assName.Equals(dll, StringComparison.Ordinal)); + if (!reserved) + { + Debug.Log($"[FilterHotFixAssemblies] filter assembly:{assName}"); + } + return reserved; + }).ToArray(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta new file mode 100644 index 00000000..2ab4ba54 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9dec2922e3df5464aa047b636eb19e0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs new file mode 100644 index 00000000..f01a4dc0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs @@ -0,0 +1,32 @@ +#if UNITY_EDITOR +using System; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +namespace HybridCLR.Editor.BuildProcessors +{ + + public class MsvcStdextWorkaround : IPreprocessBuildWithReport + { + const string kWorkaroundFlag = "/D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS"; + + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + var clEnv = Environment.GetEnvironmentVariable("_CL_"); + + if (string.IsNullOrEmpty(clEnv)) + { + Environment.SetEnvironmentVariable("_CL_", kWorkaroundFlag); + } + else if (!clEnv.Contains(kWorkaroundFlag)) + { + clEnv += " " + kWorkaroundFlag; + Environment.SetEnvironmentVariable("_CL_", clEnv); + } + } + } +} + +#endif // UNITY_EDITOR diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta new file mode 100644 index 00000000..ef28c516 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bff6cadf0b8db54b87ba51b24d080f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs new file mode 100644 index 00000000..b376fc51 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs @@ -0,0 +1,189 @@ +using HybridCLR.Editor.UnityBinFileReader; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Android; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.UnityLinker; +using UnityEngine; +using UnityFS; +#if !UNITY_2023_1_OR_NEWER +using UnityEditor.Il2Cpp; +#endif + +namespace HybridCLR.Editor.BuildProcessors +{ + public class PatchScriptingAssemblyList : +#if UNITY_ANDROID + IPostGenerateGradleAndroidProject, +#elif UNITY_OPENHARMONY + UnityEditor.OpenHarmony.IPostGenerateOpenHarmonyProject, +#endif + IPostprocessBuildWithReport +#if !UNITY_2021_1_OR_NEWER && UNITY_WEBGL + , IIl2CppProcessor +#endif + +#if UNITY_PS5 + , IUnityLinkerProcessor +#endif + + { + public int callbackOrder => 0; + + public void OnPostGenerateGradleAndroidProject(string path) + { + // 如果直接打包apk,没有机会在PostprocessBuild中修改ScriptingAssemblies.json。 + // 因此需要在这个时机处理 + // Unity有bug,偶然情况下会传入apk的路径,导致替换失败 + if (Directory.Exists(path)) + { + PathScriptingAssembilesFile(path); + } + else + { + PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Library"); + } + } + +#if UNITY_OPENHARMONY + + public void OnPostGenerateOpenHarmonyProject(string path) + { + OnPostGenerateGradleAndroidProject(path); + } + +#endif + + public void OnPostprocessBuild(BuildReport report) + { + // 如果target为Android,由于已经在OnPostGenerateGradelAndroidProject中处理过, + // 这里不再重复处理 +#if !UNITY_ANDROID && !UNITY_WEBGL && !UNITY_OPENHARMONY + PathScriptingAssembilesFile(report.summary.outputPath); +#endif + } + +#if UNITY_PS5 + /// + /// 打包模式如果是 Package 需要在这个阶段提前处理 .json , PC Hosted 和 GP5 模式不受影响 + /// + + public string GenerateAdditionalLinkXmlFile(UnityEditor.Build.Reporting.BuildReport report, UnityEditor.UnityLinker.UnityLinkerBuildPipelineData data) + { + string path = $"{SettingsUtil.ProjectDir}/Library/PlayerDataCache/PS5/Data"; + PathScriptingAssembilesFile(path); + return null; + } +#endif + public void PathScriptingAssembilesFile(string path) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[PatchScriptingAssemblyList] disabled"); + return; + } + Debug.Log($"[PatchScriptingAssemblyList]. path:{path}"); + if (!Directory.Exists(path)) + { + path = Path.GetDirectoryName(path); + Debug.Log($"[PatchScriptingAssemblyList] get path parent:{path}"); + } +#if UNITY_2020_1_OR_NEWER + AddHotFixAssembliesToScriptingAssembliesJson(path); +#else + AddHotFixAssembliesToBinFile(path); +#endif + } + + private void AddHotFixAssembliesToScriptingAssembliesJson(string path) + { + Debug.Log($"[PatchScriptingAssemblyList]. path:{path}"); + /* + * ScriptingAssemblies.json 文件中记录了所有的dll名称,此列表在游戏启动时自动加载, + * 不在此列表中的dll在资源反序列化时无法被找到其类型 + * 因此 OnFilterAssemblies 中移除的条目需要再加回来 + */ + string[] jsonFiles = Directory.GetFiles(path, SettingsUtil.ScriptingAssembliesJsonFile, SearchOption.AllDirectories); + + if (jsonFiles.Length == 0) + { + Debug.LogWarning($"can not find file {SettingsUtil.ScriptingAssembliesJsonFile}"); + return; + } + + foreach (string file in jsonFiles) + { + var patcher = new ScriptingAssembliesJsonPatcher(); + patcher.Load(file); + patcher.AddScriptingAssemblies(SettingsUtil.HotUpdateAssemblyFilesIncludePreserved); + patcher.Save(file); + } + } + private void AddHotFixAssembliesToBinFile(string path) + { +#if UNITY_STANDALONE_OSX + path = Path.GetDirectoryName(path); +#endif + if (AddHotFixAssembliesToGlobalgamemanagers(path)) + { + return; + } + if (AddHotFixAssembliesTodataunity3d(path)) + { + return; + } + Debug.LogError($"[PatchScriptingAssemblyList] can not find file '{SettingsUtil.GlobalgamemanagersBinFile}' or '{SettingsUtil.Dataunity3dBinFile}' in '{path}'"); + } + + private bool AddHotFixAssembliesToGlobalgamemanagers(string path) + { + string[] binFiles = Directory.GetFiles(path, SettingsUtil.GlobalgamemanagersBinFile, SearchOption.AllDirectories); + + if (binFiles.Length == 0) + { + return false; + } + + foreach (string binPath in binFiles) + { + var binFile = new UnityBinFile(); + binFile.Load(binPath); + binFile.AddScriptingAssemblies(SettingsUtil.HotUpdateAssemblyFilesIncludePreserved); + binFile.Save(binPath); + Debug.Log($"[PatchScriptingAssemblyList] patch {binPath}"); + } + return true; + } + + private bool AddHotFixAssembliesTodataunity3d(string path) + { + string[] binFiles = Directory.GetFiles(path, SettingsUtil.Dataunity3dBinFile, SearchOption.AllDirectories); + + if (binFiles.Length == 0) + { + return false; + } + + foreach (string binPath in binFiles) + { + var patcher = new Dataunity3dPatcher(); + patcher.ApplyPatch(binPath, SettingsUtil.HotUpdateAssemblyFilesIncludePreserved); + Debug.Log($"[PatchScriptingAssemblyList] patch {binPath}"); + } + return true; + } + +#if UNITY_WEBGL && !UNITY_2022_3_OR_NEWER + public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data) + { + PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Temp/StagingArea/Data"); + } +#endif + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta new file mode 100644 index 00000000..0affdc71 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bb6e2908d8948648979c9ff6bb7937d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs new file mode 100644 index 00000000..a124e34b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public class ScriptingAssembliesJsonPatcher + { + [Serializable] + private class ScriptingAssemblies + { + public List names; + public List types; + } + + private string _file; + ScriptingAssemblies _scriptingAssemblies; + + public void Load(string file) + { + _file = file; + string content = File.ReadAllText(file); + _scriptingAssemblies = JsonUtility.FromJson(content); + } + + public void AddScriptingAssemblies(List assemblies) + { + foreach (string name in assemblies) + { + if (!_scriptingAssemblies.names.Contains(name)) + { + _scriptingAssemblies.names.Add(name); + _scriptingAssemblies.types.Add(16); // user dll type + Debug.Log($"[PatchScriptAssembliesJson] add hotfix assembly:{name} to {_file}"); + } + } + } + + public void Save(string jsonFile) + { + string content = JsonUtility.ToJson(_scriptingAssemblies); + + File.WriteAllText(jsonFile, content); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta new file mode 100644 index 00000000..c4c07e0f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4455f7304f8678f408dd6cf21734f55e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta new file mode 100644 index 00000000..c61c7789 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 92f51c069d2607447ae2f61de80540fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs new file mode 100644 index 00000000..b4dfb416 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs @@ -0,0 +1,132 @@ +using HybridCLR.Editor.AOT; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.AOT.Analyzer; + public static class AOTReferenceGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/AOTGenericReference", priority = 102)] + public static void CompileAndGenerateAOTGenericReference() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + GenerateAOTGenericReference(target); + } + + /// + /// 计算热更代码中的泛型引用 + /// + /// + public static void GenerateAOTGenericReference(BuildTarget target) + { + var gs = SettingsUtil.HybridCLRSettings; + List hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames); + var analyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration), + Collector = collector, + }); + + analyzer.Run(); + + var writer = new GenericReferenceWriter(); + writer.Write(analyzer.AotGenericTypes.ToList(), analyzer.AotGenericMethods.ToList(), $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}"); + AssetDatabase.Refresh(); + } + + + + //[MenuItem("HybridCLR/Generate/AOTGenericReference2", priority = 103)] + //public static void GeneratedAOTGenericReferenceExcludeExists() + //{ + // GeneratedAOTGenericReferenceExcludeExists(EditorUserBuildSettings.activeBuildTarget); + //} + + /// + /// 计算热更新代码中的泛型引用,但排除AOT已经存在的泛型引用 + /// + /// + /// + public static void GeneratedAOTGenericReferenceExcludeExistsAOTClassAndMethods(BuildTarget target) + { + + var gs = SettingsUtil.HybridCLRSettings; + List hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + AssemblyReferenceDeepCollector hotUpdateCollector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames); + var hotUpdateAnalyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(10, gs.maxGenericReferenceIteration), + Collector = hotUpdateCollector, + }); + + hotUpdateAnalyzer.Run(); + + + string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + List aotAssemblyNames = Directory.Exists(aotDllDir) ? + Directory.GetFiles(aotDllDir, "*.dll", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToList() + : new List(); + if (aotAssemblyNames.Count == 0) + { + throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/AOTGenericReference`"); + } + AssemblyReferenceDeepCollector aotCollector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames); + var aotAnalyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(10, gs.maxGenericReferenceIteration), + Collector = aotCollector, + ComputeAotAssembly = true, + }); + + aotAnalyzer.Run(); + + var (resultTypes, resultMethods) = ExcludeExistAOTGenericTypeAndMethodss(hotUpdateAnalyzer.AotGenericTypes.ToList(), hotUpdateAnalyzer.AotGenericMethods.ToList(), aotAnalyzer.AotGenericTypes.ToList(), aotAnalyzer.AotGenericMethods.ToList()); + var writer = new GenericReferenceWriter(); + writer.Write(resultTypes, resultMethods, $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}"); + AssetDatabase.Refresh(); + } + + + private static (List, List) ExcludeExistAOTGenericTypeAndMethodss(List hotUpdateTypes, List hotUpdateMethods, List aotTypes, List aotMethods) + { + var types = new List(); + + var typeSig2Type = hotUpdateTypes.ToDictionary(t => t.Type.DefinitionAssembly.Name + ":" + t.ToTypeSig(), t => t); + foreach (var t in aotTypes) + { + string key = t.Type.DefinitionAssembly.Name + ":" + t.ToTypeSig(); + if (typeSig2Type.TryGetValue(key, out var removedType)) + { + typeSig2Type.Remove(key); + Debug.Log($"remove AOT type:{removedType.ToTypeSig()} "); + } + } + + var methodSig2Method = hotUpdateMethods.ToDictionary(m => m.Method.DeclaringType.DefinitionAssembly.Name + ":" + m.ToMethodSpec().ToString(), m => m); + foreach (var m in aotMethods) + { + string key = m.Method.DeclaringType.DefinitionAssembly.Name + ":" + m.ToMethodSpec().ToString(); + if (methodSig2Method.TryGetValue(key, out var removedMethod)) + { + methodSig2Method.Remove(key); + Debug.Log($"remove AOT method:{removedMethod.ToMethodSpec()} "); + } + } + + return (typeSig2Type.Values.ToList(), methodSig2Method.Values.ToList()); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta new file mode 100644 index 00000000..39760e8f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b464872c07f6ba4f9a4e4a02ca9a28c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs new file mode 100644 index 00000000..583368e1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Player; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + public class CompileDllCommand + { + public static void CompileDll(string buildDir, BuildTarget target, bool developmentBuild) + { + var group = BuildPipeline.GetBuildTargetGroup(target); + + ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings(); + scriptCompilationSettings.group = group; + scriptCompilationSettings.target = target; + scriptCompilationSettings.options = developmentBuild ? ScriptCompilationOptions.DevelopmentBuild : ScriptCompilationOptions.None; + Directory.CreateDirectory(buildDir); + ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, buildDir); +#if UNITY_2022 + UnityEditor.EditorUtility.ClearProgressBar(); +#endif + Debug.Log($"compile finish!!! buildDir:{buildDir} target:{target} development:{developmentBuild}"); + } + + public static void CompileDll(BuildTarget target) + { + CompileDll(target, EditorUserBuildSettings.development); + } + + public static void CompileDll(BuildTarget target, bool developmentBuild) + { + CompileDll(SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), target, developmentBuild); + } + + [MenuItem("HybridCLR/CompileDll/ActiveBuildTarget", priority = 100)] + public static void CompileDllActiveBuildTarget() + { + CompileDll(EditorUserBuildSettings.activeBuildTarget, EditorUserBuildSettings.development); + } + + [MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Release", priority = 102)] + public static void CompileDllActiveBuildTargetRelease() + { + CompileDll(EditorUserBuildSettings.activeBuildTarget, false); + } + + [MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Development", priority = 104)] + public static void CompileDllActiveBuildTargetDevelopment() + { + CompileDll(EditorUserBuildSettings.activeBuildTarget, true); + } + + [MenuItem("HybridCLR/CompileDll/Win32", priority = 200)] + public static void CompileDllWin32() + { + CompileDll(BuildTarget.StandaloneWindows); + } + + [MenuItem("HybridCLR/CompileDll/Win64", priority = 201)] + public static void CompileDllWin64() + { + CompileDll(BuildTarget.StandaloneWindows64); + } + + [MenuItem("HybridCLR/CompileDll/MacOS", priority = 202)] + public static void CompileDllMacOS() + { + CompileDll(BuildTarget.StandaloneOSX); + } + + [MenuItem("HybridCLR/CompileDll/Linux", priority = 203)] + public static void CompileDllLinux() + { + CompileDll(BuildTarget.StandaloneLinux64); + } + + [MenuItem("HybridCLR/CompileDll/Android", priority = 210)] + public static void CompileDllAndroid() + { + CompileDll(BuildTarget.Android); + } + + [MenuItem("HybridCLR/CompileDll/IOS", priority = 220)] + public static void CompileDllIOS() + { + CompileDll(BuildTarget.iOS); + } + + [MenuItem("HybridCLR/CompileDll/WebGL", priority = 230)] + public static void CompileDllWebGL() + { + CompileDll(BuildTarget.WebGL); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta new file mode 100644 index 00000000..88b2e303 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf11b6c8bbc5afd4cb4a11921e5bd81e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs new file mode 100644 index 00000000..44890ee8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs @@ -0,0 +1,33 @@ +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Settings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + + public static class Il2CppDefGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/Il2CppDef", priority = 104)] + public static void GenerateIl2CppDef() + { + var options = new Il2CppDef.Il2CppDefGenerator.Options() + { + UnityVersion = Application.unityVersion, + HotUpdateAssemblies = SettingsUtil.HotUpdateAssemblyNamesIncludePreserved, + UnityVersionTemplateFile = $"{SettingsUtil.TemplatePathInPackage}/UnityVersion.h.tpl", + UnityVersionOutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/UnityVersion.h", + AssemblyManifestTemplateFile = $"{SettingsUtil.TemplatePathInPackage}/AssemblyManifest.cpp.tpl", + AssemblyManifestOutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/AssemblyManifest.cpp", + }; + + var g = new Il2CppDef.Il2CppDefGenerator(options); + g.Generate(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta new file mode 100644 index 00000000..21f94275 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5165a065d05497c43a2fff885f31ed07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs new file mode 100644 index 00000000..c545ff52 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs @@ -0,0 +1,39 @@ +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.Link.Analyzer; + + public static class LinkGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/LinkXml", priority = 100)] + public static void GenerateLinkXml() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + GenerateLinkXml(target); + } + + public static void GenerateLinkXml(BuildTarget target) + { + var ls = SettingsUtil.HybridCLRSettings; + + List hotfixAssemblies = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + var analyzer = new Analyzer(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotfixAssemblies)); + var refTypes = analyzer.CollectRefs(hotfixAssemblies); + + Debug.Log($"[LinkGeneratorCommand] hotfix assembly count:{hotfixAssemblies.Count}, ref type count:{refTypes.Count} output:{Application.dataPath}/{ls.outputLinkFile}"); + var linkXmlWriter = new LinkXmlWriter(); + linkXmlWriter.Write($"{Application.dataPath}/{ls.outputLinkFile}", refTypes); + AssetDatabase.Refresh(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta new file mode 100644 index 00000000..0eeba1f9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f5b96abdbc4c424eb1bc3bc34b3a1a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs new file mode 100644 index 00000000..0ebdce0a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs @@ -0,0 +1,98 @@ +using HybridCLR.Editor; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.MethodBridge; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.MethodBridge.Analyzer; + public class MethodBridgeGeneratorCommand + { + + public static void CleanIl2CppBuildCache() + { + string il2cppBuildCachePath = SettingsUtil.Il2CppBuildCacheDir; + if (!Directory.Exists(il2cppBuildCachePath)) + { + return; + } + Debug.Log($"clean il2cpp build cache:{il2cppBuildCachePath}"); + Directory.Delete(il2cppBuildCachePath, true); + } + + private static void GenerateMethodBridgeCppFile(IReadOnlyCollection genericMethods, List reversePInvokeMethods, IReadOnlyCollection calliMethodSignatures, string tempFile, string outputFile) + { + string templateCode = File.ReadAllText(tempFile, Encoding.UTF8); + var g = new Generator(new Generator.Options() + { + TemplateCode = templateCode, + OutputFile = outputFile, + GenericMethods = genericMethods, + ReversePInvokeMethods = reversePInvokeMethods, + CalliMethodSignatures = calliMethodSignatures, + Development = EditorUserBuildSettings.development, + }); + + g.Generate(); + Debug.LogFormat("[MethodBridgeGeneratorCommand] output:{0}", outputFile); + } + + [MenuItem("HybridCLR/Generate/MethodBridgeAndReversePInvokeWrapper", priority = 101)] + public static void GenerateMethodBridgeAndReversePInvokeWrapper() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + GenerateMethodBridgeAndReversePInvokeWrapper(target); + } + + public static void GenerateMethodBridgeAndReversePInvokeWrapper(BuildTarget target) + { + string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + List aotAssemblyNames = Directory.Exists(aotDllDir) ? + Directory.GetFiles(aotDllDir, "*.dll", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToList() + : new List(); + if (aotAssemblyNames.Count == 0) + { + throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/MethodBridge`"); + } + AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames); + + var methodBridgeAnalyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(20, SettingsUtil.HybridCLRSettings.maxMethodBridgeGenericIteration), + Collector = collector, + }); + + methodBridgeAnalyzer.Run(); + + List hotUpdateDlls = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + var cache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDlls)); + + var reversePInvokeAnalyzer = new MonoPInvokeCallbackAnalyzer(cache, hotUpdateDlls); + reversePInvokeAnalyzer.Run(); + + var calliAnalyzer = new CalliAnalyzer(cache, hotUpdateDlls); + calliAnalyzer.Run(); + var pinvokeAnalyzer = new PInvokeAnalyzer(cache, hotUpdateDlls); + pinvokeAnalyzer.Run(); + var callPInvokeMethodSignatures = pinvokeAnalyzer.PInvokeMethodSignatures; + + string templateFile = $"{SettingsUtil.TemplatePathInPackage}/MethodBridge.cpp.tpl"; + string outputFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp"; + + var callNativeMethodSignatures = calliAnalyzer.CalliMethodSignatures.Concat(pinvokeAnalyzer.PInvokeMethodSignatures).ToList(); + GenerateMethodBridgeCppFile(methodBridgeAnalyzer.GenericMethods, reversePInvokeAnalyzer.ReversePInvokeMethods, callNativeMethodSignatures, templateFile, outputFile); + + CleanIl2CppBuildCache(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta new file mode 100644 index 00000000..98279979 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46bc62d5236f5e941850776c435a9560 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs new file mode 100644 index 00000000..32788026 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; + +namespace HybridCLR.Editor.Commands +{ + public static class PrebuildCommand + { + /// + /// 按照必要的顺序,执行所有生成操作,适合打包前操作 + /// + [MenuItem("HybridCLR/Generate/All", priority = 200)] + public static void GenerateAll() + { + var installer = new Installer.InstallerController(); + if (!installer.HasInstalledHybridCLR()) + { + throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'"); + } + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target, EditorUserBuildSettings.development); + Il2CppDefGeneratorCommand.GenerateIl2CppDef(); + + // 这几个生成依赖HotUpdateDlls + LinkGeneratorCommand.GenerateLinkXml(target); + + // 生成裁剪后的aot dll + StripAOTDllCommand.GenerateStripedAOTDlls(target); + + // 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll + MethodBridgeGeneratorCommand.GenerateMethodBridgeAndReversePInvokeWrapper(target); + AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta new file mode 100644 index 00000000..a27aea2b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c20f09bfbe3f32143aae872d3813d9e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs new file mode 100644 index 00000000..8e6560a9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs @@ -0,0 +1,196 @@ +using HybridCLR.Editor.BuildProcessors; +using HybridCLR.Editor.Installer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using static UnityEngine.Networking.UnityWebRequest; + +namespace HybridCLR.Editor.Commands +{ + public static class StripAOTDllCommand + { + [MenuItem("HybridCLR/Generate/AOTDlls", priority = 105)] + public static void GenerateStripedAOTDlls() + { + GenerateStripedAOTDlls(EditorUserBuildSettings.activeBuildTarget); + } + + static BuildOptions GetBuildPlayerOptions(BuildTarget buildTarget) + { + BuildOptions options = BuildOptions.None; + bool development = EditorUserBuildSettings.development; + if (development) + { + options |= BuildOptions.Development; + } + + if (EditorUserBuildSettings.allowDebugging && development) + { + options |= BuildOptions.AllowDebugging; + } + + if (EditorUserBuildSettings.connectProfiler && (development || buildTarget == BuildTarget.WSAPlayer)) + { + options |= BuildOptions.ConnectWithProfiler; + } + + if (EditorUserBuildSettings.buildWithDeepProfilingSupport && development) + { + options |= BuildOptions.EnableDeepProfilingSupport; + } + +#if UNITY_2021_2_OR_NEWER + options |= BuildOptions.CleanBuildCache; +#endif + + return options; + } + + private static string GetLocationPathName(string buildDir, BuildTarget target) + { + switch(target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: return $"{buildDir}/{PlayerSettings.productName}.exe"; + case BuildTarget.StandaloneOSX: return buildDir; + case BuildTarget.iOS: return buildDir; + case BuildTarget.Android: return buildDir; + case BuildTarget.StandaloneLinux64: return $"{buildDir}/{PlayerSettings.productName}"; + default: return buildDir; + } + } + + public static void GenerateStripedAOTDlls(BuildTarget target) + { + string outputPath = $"{SettingsUtil.HybridCLRDataDir}/StrippedAOTDllsTempProj/{target}"; + BashUtil.RemoveDir(outputPath); + + var buildOptions = GetBuildPlayerOptions(target); + + bool oldExportAndroidProj = EditorUserBuildSettings.exportAsGoogleAndroidProject; +#if UNITY_EDITOR_OSX + bool oldCreateSolution = UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject; +#elif UNITY_EDITOR_WIN + bool oldCreateSolution = UnityEditor.WindowsStandalone.UserBuildSettings.createSolution; +#endif +#if TUANJIE_2022_3_OR_NEWER + bool oldOpenHarmonyProj = EditorUserBuildSettings.exportAsOpenHarmonyProject; +#endif + bool oldBuildScriptsOnly = EditorUserBuildSettings.buildScriptsOnly; + + string oldBuildLocation = EditorUserBuildSettings.GetBuildLocation(target); + try + { + CheckSettings.DisableMethodBridgeDevelopmentFlagChecking = true; + EditorUserBuildSettings.buildScriptsOnly = true; + + string location = GetLocationPathName(outputPath, target); + EditorUserBuildSettings.SetBuildLocation(target, location); + + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + { + #if UNITY_EDITOR_WIN + UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = true; + #endif + break; + } + case BuildTarget.StandaloneOSX: + { + #if UNITY_EDITOR_OSX + UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = true; + #endif + break; + } + #if TUANJIE_2022_3_OR_NEWER + case BuildTarget.HMIAndroid: + #endif + case BuildTarget.Android: + { + EditorUserBuildSettings.exportAsGoogleAndroidProject = true; + break; + } + #if TUANJIE_2022_3_OR_NEWER + case BuildTarget.OpenHarmony: + { + EditorUserBuildSettings.exportAsOpenHarmonyProject = true; + break; + } + #endif + } + + Debug.Log($"GenerateStripedAOTDlls build option:{buildOptions}"); + + BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions() + { + scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(), + locationPathName = location, + options = buildOptions, + target = target, + targetGroup = BuildPipeline.GetBuildTargetGroup(target), +#if UNITY_2021_1_OR_NEWER + subtarget = (int)EditorUserBuildSettings.standaloneBuildSubtarget, +#endif + }; + + var report = BuildPipeline.BuildPlayer(buildPlayerOptions); + + + + if (report.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + { + throw new Exception("GenerateStripedAOTDlls failed"); + } + } + finally + { + CheckSettings.DisableMethodBridgeDevelopmentFlagChecking = false; + EditorUserBuildSettings.buildScriptsOnly = oldBuildScriptsOnly; + EditorUserBuildSettings.SetBuildLocation(target, oldBuildLocation); + + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + { +#if UNITY_EDITOR_WIN + UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = oldCreateSolution; +#endif + break; + } + case BuildTarget.StandaloneOSX: + { +#if UNITY_EDITOR_OSX + UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = oldCreateSolution; +#endif + break; + } +#if TUANJIE_2022_3_OR_NEWER + case BuildTarget.HMIAndroid: +#endif + case BuildTarget.Android: + { + EditorUserBuildSettings.exportAsGoogleAndroidProject = oldExportAndroidProj; + break; + } +#if TUANJIE_2022_3_OR_NEWER + case BuildTarget.OpenHarmony: + { + EditorUserBuildSettings.exportAsOpenHarmonyProject = oldOpenHarmonyProj; + break; + } +#endif + } + } + Debug.Log($"GenerateStripedAOTDlls target:{target} path:{outputPath}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta new file mode 100644 index 00000000..3979b80d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21fb0a02f23185141a4a3df67fe61789 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs new file mode 100644 index 00000000..38e83e39 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs @@ -0,0 +1,28 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor +{ + public static class HashUtil + { + public static int CombineHash(int hash1, int hash2) + { + return hash1 * 1566083941 + hash2; + } + + public static int ComputHash(List sigs) + { + int hash = 135781321; + TypeEqualityComparer tc = TypeEqualityComparer.Instance; + foreach (var sig in sigs) + { + hash = hash * 1566083941 + tc.GetHashCode(sig); + } + return hash; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta new file mode 100644 index 00000000..9cdce22f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d4ae4a5c0bba49469c525887d812717 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate.meta new file mode 100644 index 00000000..7b7bf3d8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e60a8b17b0e23a94a8ae875716208030 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs new file mode 100644 index 00000000..2459b486 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs @@ -0,0 +1,109 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.HotUpdate +{ + public class MissingMetadataChecker + { + private readonly HashSet _aotAssNames; + + private readonly HashSet _hotUpdateAssNames; + + private readonly AssemblyCache _assCache; + + public MissingMetadataChecker(string aotDllDir, IEnumerable hotUpdateAssNames) + { + + _hotUpdateAssNames = new HashSet(hotUpdateAssNames ?? new List()); + _aotAssNames = new HashSet(); + foreach (var aotFile in Directory.GetFiles(aotDllDir, "*.dll")) + { + string aotAssName = Path.GetFileNameWithoutExtension(aotFile); + if (_hotUpdateAssNames.Contains(aotAssName)) + { + continue; + } + _aotAssNames.Add(aotAssName); + } + _assCache = new AssemblyCache(new PathAssemblyResolver(aotDllDir)); + } + + public bool Check(string hotUpdateDllPath) + { + bool anyMissing = false; + + ModuleDef mod = ModuleDefMD.Load(File.ReadAllBytes(hotUpdateDllPath), _assCache.ModCtx); + + foreach (var refass in mod.GetAssemblyRefs()) + { + string refAssName = refass.Name; + if (_aotAssNames.Contains(refAssName)) + { + _assCache.LoadModule(refass.Name, true); + } + else if (!_hotUpdateAssNames.Contains(refAssName)) + { + UnityEngine.Debug.LogError($"Missing AOT Assembly: {refAssName}"); + anyMissing = true; + } + } + + + foreach (TypeRef typeRef in mod.GetTypeRefs()) + { + string defAssName = typeRef.DefinitionAssembly.Name; + if (!_aotAssNames.Contains(defAssName)) + { + continue; + } + if (typeRef.ResolveTypeDef() == null) + { + UnityEngine.Debug.LogError($"Missing Type: {typeRef.FullName}"); + anyMissing = true; + } + } + + foreach (IMethodDefOrRef memberRef in mod.GetMemberRefs()) + { + if (memberRef.DeclaringType.DefinitionAssembly == null) + { + continue; + } + string defAssName = memberRef.DeclaringType.DefinitionAssembly.Name; + if (!_aotAssNames.Contains(defAssName)) + { + continue; + } + if (memberRef.IsField) + { + IField field = (IField)memberRef; + if (field.ResolveFieldDef() == null) + { + UnityEngine.Debug.LogError($"Missing Field: {memberRef.FullName}"); + anyMissing = true; + } + } + else if (memberRef.IsMethod) + { + TypeSig declaringTypeSig = memberRef.DeclaringType.ToTypeSig(); + if (memberRef.ResolveMethodDef() == null) + { + if (declaringTypeSig.ElementType == ElementType.Array || declaringTypeSig.ElementType == ElementType.SZArray) + { + continue; + } + UnityEngine.Debug.LogError($"Missing Method: {memberRef.FullName}"); + anyMissing = true; + } + } + } + return !anyMissing; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs.meta new file mode 100644 index 00000000..bc58ca1a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HotUpdate/MissingMetadataChecker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdd260aca2a6deb44b20210f01faa86b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef new file mode 100644 index 00000000..4612a6d9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "HybridCLR.Editor", + "rootNamespace": "", + "references": [ + "HybridCLR.Runtime" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta new file mode 100644 index 00000000..be1417b6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2373f786d14518f44b0f475db77ba4de +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta new file mode 100644 index 00000000..dc951cad --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da46bc9f1a4dece41a5c193166be9a30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs new file mode 100644 index 00000000..06327507 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs @@ -0,0 +1,107 @@ +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Template; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Il2CppDef +{ + public class Il2CppDefGenerator + { + public class Options + { + public List HotUpdateAssemblies { get; set; } + + public string UnityVersionTemplateFile { get; set; } + + public string UnityVersionOutputFile { get; set; } + + public string AssemblyManifestTemplateFile { get; set; } + + public string AssemblyManifestOutputFile { get; set; } + + public string UnityVersion { get; set; } + } + + private readonly Options _options; + public Il2CppDefGenerator(Options options) + { + _options = options; + } + + + private static readonly Regex s_unityVersionPat = new Regex(@"(\d+)\.(\d+)\.(\d+)"); + + public void Generate() + { + GenerateIl2CppConfig(); + GeneratePlaceHolderAssemblies(); + } + + private void GenerateIl2CppConfig() + { + var frr = new FileRegionReplace(File.ReadAllText(_options.UnityVersionTemplateFile)); + + List lines = new List(); + + var match = s_unityVersionPat.Matches(_options.UnityVersion)[0]; + int majorVer = int.Parse(match.Groups[1].Value); + int minorVer1 = int.Parse(match.Groups[2].Value); + int minorVer2 = int.Parse(match.Groups[3].Value); + + lines.Add($"#define HYBRIDCLR_UNITY_VERSION {majorVer}{minorVer1.ToString("D2")}{minorVer2.ToString("D2")}"); + lines.Add($"#define HYBRIDCLR_UNITY_{majorVer} 1"); + for (int ver = 2019; ver <= 2023; ver++) + { + if (majorVer >= ver) + { + lines.Add($"#define HYBRIDCLR_UNITY_{ver}_OR_NEW 1"); + } + } + for (int ver = 6000; ver <= 6100; ver++) + { + if (majorVer >= ver) + { + lines.Add($"#define HYBRIDCLR_UNITY_{ver}_OR_NEW 1"); + } + } + +#if TUANJIE_1_1_OR_NEWER + var tuanjieMatch = Regex.Matches(Application.tuanjieVersion, @"(\d+)\.(\d+)\.(\d+)"); + int tuanjieMajorVer = int.Parse(tuanjieMatch[0].Groups[1].Value); + int tuanjieMinorVer1 = int.Parse(tuanjieMatch[0].Groups[2].Value); + int tuanjieMinorVer2 = int.Parse(tuanjieMatch[0].Groups[3].Value); + lines.Add($"#define HYBRIDCLR_TUANJIE_VERSION {tuanjieMajorVer}{tuanjieMinorVer1.ToString("D2")}{tuanjieMinorVer2.ToString("D2")}"); +#elif TUANJIE_2022_3_OR_NEWER + lines.Add($"#define HYBRIDCLR_TUANJIE_VERSION 10000"); +#endif + + frr.Replace("UNITY_VERSION", string.Join("\n", lines)); + + frr.Commit(_options.UnityVersionOutputFile); + Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.UnityVersionOutputFile}"); + } + + private void GeneratePlaceHolderAssemblies() + { + var frr = new FileRegionReplace(File.ReadAllText(_options.AssemblyManifestTemplateFile)); + + List lines = new List(); + + foreach (var ass in _options.HotUpdateAssemblies) + { + lines.Add($"\t\t\"{ass}\","); + } + + frr.Replace("PLACE_HOLDER", string.Join("\n", lines)); + + frr.Commit(_options.AssemblyManifestOutputFile); + Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.AssemblyManifestOutputFile}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta new file mode 100644 index 00000000..b7fe9d71 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 590419ee7e82ac24cbac9b8a48891fe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta new file mode 100644 index 00000000..b27774d7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a2c8f84b297371d4cbcd5ca655bf360d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs new file mode 100644 index 00000000..90e42597 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Installer +{ + public static class BashUtil + { + public static int RunCommand(string workingDir, string program, string[] args, bool log = true) + { + using (Process p = new Process()) + { + p.StartInfo.WorkingDirectory = workingDir; + p.StartInfo.FileName = program; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + string argsStr = string.Join(" ", args.Select(arg => "\"" + arg + "\"")); + p.StartInfo.Arguments = argsStr; + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] run => {program} {argsStr}"); + } + p.Start(); + p.WaitForExit(); + return p.ExitCode; + } + } + + + public static (int ExitCode, string StdOut, string StdErr) RunCommand2(string workingDir, string program, string[] args, bool log = true) + { + using (Process p = new Process()) + { + p.StartInfo.WorkingDirectory = workingDir; + p.StartInfo.FileName = program; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + string argsStr = string.Join(" ", args); + p.StartInfo.Arguments = argsStr; + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] run => {program} {argsStr}"); + } + p.Start(); + p.WaitForExit(); + + string stdOut = p.StandardOutput.ReadToEnd(); + string stdErr = p.StandardError.ReadToEnd(); + return (p.ExitCode, stdOut, stdErr); + } + } + + + public static void RemoveDir(string dir, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] RemoveDir dir:{dir}"); + } + + int maxTryCount = 5; + for (int i = 0; i < maxTryCount; ++i) + { + try + { + if (!Directory.Exists(dir)) + { + return; + } + foreach (var file in Directory.GetFiles(dir)) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + foreach (var subDir in Directory.GetDirectories(dir)) + { + RemoveDir(subDir); + } + Directory.Delete(dir, true); + break; + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[BashUtil] RemoveDir:{dir} with exception:{e}. try count:{i}"); + Thread.Sleep(100); + } + } + } + + public static void RecreateDir(string dir) + { + if(Directory.Exists(dir)) + { + RemoveDir(dir, true); + } + Directory.CreateDirectory(dir); + } + + private static void CopyWithCheckLongFile(string srcFile, string dstFile) + { + var maxPathLength = 255; +#if UNITY_EDITOR_OSX + maxPathLength = 1024; +#endif + if (srcFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"srcFile:{srcFile} path is too long. skip copy!"); + return; + } + if (dstFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"dstFile:{dstFile} path is too long. skip copy!"); + return; + } + File.Copy(srcFile, dstFile); + } + + public static void CopyDir(string src, string dst, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] CopyDir {src} => {dst}"); + } + RemoveDir(dst); + Directory.CreateDirectory(dst); + foreach(var file in Directory.GetFiles(src)) + { + CopyWithCheckLongFile(file, $"{dst}/{Path.GetFileName(file)}"); + } + foreach(var subDir in Directory.GetDirectories(src)) + { + CopyDir(subDir, $"{dst}/{Path.GetFileName(subDir)}"); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta new file mode 100644 index 00000000..ef7e11d6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 960a0257c3a17f64b810193308ce1558 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs new file mode 100644 index 00000000..fd40dbde --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEngine; +using Debug = UnityEngine.Debug; +using System.Text.RegularExpressions; +using System.Linq; +using HybridCLR.Editor.Settings; + +namespace HybridCLR.Editor.Installer +{ + + public class InstallerController + { + private const string hybridclr_repo_path = "hybridclr_repo"; + + private const string il2cpp_plus_repo_path = "il2cpp_plus_repo"; + + public int MajorVersion => _curVersion.major; + + private readonly UnityVersion _curVersion; + + private readonly HybridclrVersionManifest _versionManifest; + private readonly HybridclrVersionInfo _curDefaultVersion; + + public string PackageVersion { get; private set; } + + public string InstalledLibil2cppVersion { get; private set; } + + public InstallerController() + { + _curVersion = ParseUnityVersion(Application.unityVersion); + _versionManifest = GetHybridCLRVersionManifest(); + _curDefaultVersion = _versionManifest.versions.FirstOrDefault(v => _curVersion.isTuanjieEngine ? v.unity_version == $"{_curVersion.major}-tuanjie" : v.unity_version == _curVersion.major.ToString()); + PackageVersion = LoadPackageInfo().version; + InstalledLibil2cppVersion = ReadLocalVersion(); + } + + private HybridclrVersionManifest GetHybridCLRVersionManifest() + { + string versionFile = $"{SettingsUtil.ProjectDir}/{SettingsUtil.HybridCLRDataPathInPackage}/hybridclr_version.json"; + return JsonUtility.FromJson(File.ReadAllText(versionFile, Encoding.UTF8)); + } + + private PackageInfo LoadPackageInfo() + { + string packageJson = $"{SettingsUtil.ProjectDir}/Packages/{SettingsUtil.PackageName}/package.json"; + return JsonUtility.FromJson(File.ReadAllText(packageJson, Encoding.UTF8)); + } + + + [Serializable] + class PackageInfo + { + public string name; + + public string version; + } + + [Serializable] + class VersionDesc + { + public string branch; + + //public string hash; + } + + [Serializable] + class HybridclrVersionInfo + { + public string unity_version; + + public VersionDesc hybridclr; + + public VersionDesc il2cpp_plus; + } + + [Serializable] + class HybridclrVersionManifest + { + public List versions; + } + + private class UnityVersion + { + public int major; + public int minor1; + public int minor2; + public bool isTuanjieEngine; + + public override string ToString() + { + return $"{major}.{minor1}.{minor2}"; + } + } + + private static readonly Regex s_unityVersionPat = new Regex(@"(\d+)\.(\d+)\.(\d+)"); + + private UnityVersion ParseUnityVersion(string versionStr) + { + var matches = s_unityVersionPat.Matches(versionStr); + if (matches.Count == 0) + { + return null; + } + Match match = matches[matches.Count - 1]; + int major = int.Parse(match.Groups[1].Value); + int minor1 = int.Parse(match.Groups[2].Value); + int minor2 = int.Parse(match.Groups[3].Value); + bool isTuanjieEngine = versionStr.Contains("t"); + return new UnityVersion { major = major, minor1 = minor1, minor2 = minor2, isTuanjieEngine = isTuanjieEngine }; + } + + public string GetCurrentUnityVersionMinCompatibleVersionStr() + { + return GetMinCompatibleVersion(MajorVersion); + } + + public string GetMinCompatibleVersion(int majorVersion) + { + switch(majorVersion) + { + case 2019: return "2019.4.0"; + case 2020: return "2020.3.0"; + case 2021: return "2021.3.0"; + case 2022: return "2022.3.0"; + case 2023: return "2023.2.0"; + case 6000: return "6000.0.0"; + default: return $"2020.3.0"; + } + } + + public enum CompatibleType + { + Compatible, + MaybeIncompatible, + Incompatible, + } + + public CompatibleType GetCompatibleType() + { + UnityVersion version = _curVersion; + if (version == null) + { + return CompatibleType.Incompatible; + } + if ((version.major == 2019 && version.minor1 < 4) + || (version.major >= 2020 && version.major <= 2022 && version.minor1 < 3)) + { + return CompatibleType.MaybeIncompatible; + } + return CompatibleType.Compatible; + } + + public string HybridclrLocalVersion => _curDefaultVersion?.hybridclr?.branch; + + public string Il2cppPlusLocalVersion => _curDefaultVersion?.il2cpp_plus?.branch; + + + private string GetIl2CppPathByContentPath(string contentPath) + { + return $"{contentPath}/il2cpp"; + } + + public string ApplicationIl2cppPath => GetIl2CppPathByContentPath(EditorApplication.applicationContentsPath); + + public string LocalVersionFile => $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/libil2cpp-version.txt"; + + private string ReadLocalVersion() + { + if (!File.Exists(LocalVersionFile)) + { + return null; + } + return File.ReadAllText(LocalVersionFile, Encoding.UTF8); + } + + public void WriteLocalVersion() + { + InstalledLibil2cppVersion = PackageVersion; + File.WriteAllText(LocalVersionFile, PackageVersion, Encoding.UTF8); + Debug.Log($"Write installed version:'{PackageVersion}' to {LocalVersionFile}"); + } + + public void InstallDefaultHybridCLR() + { + InstallFromLocal(PrepareLibil2cppWithHybridclrFromGitRepo()); + } + + public bool HasInstalledHybridCLR() + { + return Directory.Exists($"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr"); + } + + private string GetUnityIl2CppDllInstallLocation() + { +#if UNITY_EDITOR_WIN + return $"{SettingsUtil.LocalIl2CppDir}/build/deploy/net471/Unity.IL2CPP.dll"; +#else + return $"{SettingsUtil.LocalIl2CppDir}/build/deploy/il2cppcore/Unity.IL2CPP.dll"; +#endif + } + + private string GetUnityIl2CppDllModifiedPath(string curVersionStr) + { +#if UNITY_EDITOR_WIN + return $"{SettingsUtil.ProjectDir}/{SettingsUtil.HybridCLRDataPathInPackage}/ModifiedUnityAssemblies/{curVersionStr}/Unity.IL2CPP-Win.dll"; +#else + return $"{SettingsUtil.ProjectDir}/{SettingsUtil.HybridCLRDataPathInPackage}/ModifiedUnityAssemblies/{curVersionStr}/Unity.IL2CPP-Mac.dll"; +#endif + } + + void CloneBranch(string workDir, string repoUrl, string branch, string repoDir) + { + BashUtil.RemoveDir(repoDir); + BashUtil.RunCommand(workDir, "git", new string[] {"clone", "-b", branch, "--depth", "1", repoUrl, repoDir}); + } + + private string PrepareLibil2cppWithHybridclrFromGitRepo() + { + string workDir = SettingsUtil.HybridCLRDataDir; + Directory.CreateDirectory(workDir); + //BashUtil.RecreateDir(workDir); + + // clone hybridclr + string hybridclrRepoURL = HybridCLRSettings.Instance.hybridclrRepoURL; + string hybridclrRepoDir = $"{workDir}/{hybridclr_repo_path}"; + CloneBranch(workDir, hybridclrRepoURL, _curDefaultVersion.hybridclr.branch, hybridclrRepoDir); + + if (!Directory.Exists(hybridclrRepoDir)) + { + throw new Exception($"clone hybridclr fail. url: {hybridclrRepoURL}"); + } + + // clone il2cpp_plus + string il2cppPlusRepoURL = HybridCLRSettings.Instance.il2cppPlusRepoURL; + string il2cppPlusRepoDir = $"{workDir}/{il2cpp_plus_repo_path}"; + CloneBranch(workDir, il2cppPlusRepoURL, _curDefaultVersion.il2cpp_plus.branch, il2cppPlusRepoDir); + + if (!Directory.Exists(il2cppPlusRepoDir)) + { + throw new Exception($"clone il2cpp_plus fail. url: {il2cppPlusRepoDir}"); + } + + Directory.Move($"{hybridclrRepoDir}/hybridclr", $"{il2cppPlusRepoDir}/libil2cpp/hybridclr"); + return $"{il2cppPlusRepoDir}/libil2cpp"; + } + + public void InstallFromLocal(string libil2cppWithHybridclrSourceDir) + { + RunInitLocalIl2CppData(ApplicationIl2cppPath, libil2cppWithHybridclrSourceDir, _curVersion); + } + + private void RunInitLocalIl2CppData(string editorIl2cppPath, string libil2cppWithHybridclrSourceDir, UnityVersion version) + { + if (GetCompatibleType() == CompatibleType.Incompatible) + { + Debug.LogError($"Incompatible with current version, minimum compatible version: {GetCurrentUnityVersionMinCompatibleVersionStr()}"); + return; + } + string workDir = SettingsUtil.HybridCLRDataDir; + Directory.CreateDirectory(workDir); + + // create LocalIl2Cpp + string localUnityDataDir = SettingsUtil.LocalUnityDataDir; + BashUtil.RecreateDir(localUnityDataDir); + + // copy MonoBleedingEdge + BashUtil.CopyDir($"{Directory.GetParent(editorIl2cppPath)}/MonoBleedingEdge", $"{localUnityDataDir}/MonoBleedingEdge", true); + + // copy il2cpp + BashUtil.CopyDir(editorIl2cppPath, SettingsUtil.LocalIl2CppDir, true); + + // replace libil2cpp + string dstLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + BashUtil.CopyDir($"{libil2cppWithHybridclrSourceDir}", dstLibil2cppDir, true); + + // clean Il2cppBuildCache + BashUtil.RemoveDir($"{SettingsUtil.ProjectDir}/Library/Il2cppBuildCache", true); + if (version.major == 2019) + { + string curVersionStr = version.ToString(); + string srcIl2CppDll = GetUnityIl2CppDllModifiedPath(curVersionStr); + if (File.Exists(srcIl2CppDll)) + { + string dstIl2CppDll = GetUnityIl2CppDllInstallLocation(); + File.Copy(srcIl2CppDll, dstIl2CppDll, true); + Debug.Log($"copy {srcIl2CppDll} => {dstIl2CppDll}"); + } + else + { + throw new Exception($"the modified Unity.IL2CPP.dll of {curVersionStr} isn't found. please install hybridclr in 2019.4.40 first, then switch to your unity version"); + } + } + if (HasInstalledHybridCLR()) + { + WriteLocalVersion(); + Debug.Log("Install Sucessfully"); + } + else + { + Debug.LogError("Installation failed!"); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta new file mode 100644 index 00000000..6b30944d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44c8627d126b30d4e9560b1f738264ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs new file mode 100644 index 00000000..1869f898 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Reflection; +using UnityEditor; +using UnityEngine; + + +namespace HybridCLR.Editor.Installer +{ + public class InstallerWindow : EditorWindow + { + private InstallerController _controller; + + private bool _installFromDir; + + private string _installLibil2cppWithHybridclrSourceDir; + + private void OnEnable() + { + _controller = new InstallerController(); + } + + private void OnGUI() + { + var rect = new Rect + { + x = EditorGUIUtility.currentViewWidth - 24, + y = 5, + width = 24, + height = 24 + }; + var content = EditorGUIUtility.IconContent("Settings"); + content.tooltip = "HybridCLR Settings"; + if (GUI.Button(rect, content, GUI.skin.GetStyle("IconButton"))) + { + SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + } + + bool hasInstall = _controller.HasInstalledHybridCLR(); + + GUILayout.Space(10f); + + EditorGUILayout.BeginVertical("box"); + EditorGUILayout.LabelField($"Installed: {hasInstall}", EditorStyles.boldLabel); + GUILayout.Space(10f); + + EditorGUILayout.LabelField($"Package Version: v{_controller.PackageVersion}"); + GUILayout.Space(5f); + EditorGUILayout.LabelField($"Installed Version: v{_controller.InstalledLibil2cppVersion ?? " Unknown"}"); + GUILayout.Space(5f); + + GUILayout.Space(10f); + + InstallerController.CompatibleType compatibleType = _controller.GetCompatibleType(); + if (compatibleType != InstallerController.CompatibleType.Incompatible) + { + if (compatibleType == InstallerController.CompatibleType.MaybeIncompatible) + { + EditorGUILayout.HelpBox($"Maybe incompatible with current version, recommend minimum compatible version:{_controller.GetCurrentUnityVersionMinCompatibleVersionStr()}", MessageType.Warning); + } + + EditorGUILayout.BeginHorizontal(); + _installFromDir = EditorGUILayout.Toggle("Copy libil2cpp from local", _installFromDir, GUILayout.MinWidth(100)); + EditorGUI.BeginDisabledGroup(!_installFromDir); + EditorGUILayout.TextField(_installLibil2cppWithHybridclrSourceDir, GUILayout.Width(400)); + if (GUILayout.Button("Choose", GUILayout.Width(100))) + { + _installLibil2cppWithHybridclrSourceDir = EditorUtility.OpenFolderPanel("Select libil2cpp", Application.dataPath, "libil2cpp"); + } + EditorGUI.EndDisabledGroup(); + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(20f); + + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Install", GUILayout.Width(100))) + { + InstallLocalHybridCLR(); + GUIUtility.ExitGUI(); + } + EditorGUILayout.EndHorizontal(); + } + else + { + EditorGUILayout.HelpBox($"Incompatible with current version, minimum compatible version:{_controller.GetCurrentUnityVersionMinCompatibleVersionStr()}", MessageType.Error); + } + + EditorGUILayout.EndVertical(); + } + + private void InstallLocalHybridCLR() + { + if (_installFromDir) + { + if (!Directory.Exists(_installLibil2cppWithHybridclrSourceDir)) + { + Debug.LogError($"Source libil2cpp:'{_installLibil2cppWithHybridclrSourceDir}' doesn't exist."); + return; + } + if (!File.Exists($"{_installLibil2cppWithHybridclrSourceDir}/il2cpp-config.h") || !File.Exists($"{_installLibil2cppWithHybridclrSourceDir}/hybridclr/RuntimeApi.cpp")) + { + Debug.LogError($"Source libil2cpp:' {_installLibil2cppWithHybridclrSourceDir} ' is invalid"); + return; + } + _controller.InstallFromLocal(_installLibil2cppWithHybridclrSourceDir); + } + else + { + _controller.InstallDefaultHybridCLR(); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta new file mode 100644 index 00000000..f404c28d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 959fbf0bb06629542969354505189240 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link.meta new file mode 100644 index 00000000..1cad1b3e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5186a137e0258034cb3832bdf6b16a70 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs new file mode 100644 index 00000000..ffbb93d7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using UnityEditor; +using UnityEngine; +using IAssemblyResolver = HybridCLR.Editor.Meta.IAssemblyResolver; + +namespace HybridCLR.Editor.Link +{ + public class Analyzer + { + private readonly IAssemblyResolver _resolver; + + public Analyzer(IAssemblyResolver resolver) + { + _resolver = resolver; + } + + public HashSet CollectRefs(List rootAssemblies) + { + var assCollector = new AssemblyCache(_resolver); + var rootAssemblyNames = new HashSet(rootAssemblies); + + var typeRefs = new HashSet(TypeEqualityComparer.Instance); + foreach (var rootAss in rootAssemblies) + { + var dnAss = assCollector.LoadModule(rootAss, false); + foreach (var type in dnAss.GetTypeRefs()) + { + if (type.DefinitionAssembly == null) + { + Debug.LogWarning($"assembly:{dnAss.Name} TypeRef {type.FullName} has no DefinitionAssembly"); + continue; + } + if (!rootAssemblyNames.Contains(type.DefinitionAssembly.Name.ToString())) + { + typeRefs.Add(type); + } + } + } + return typeRefs; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta new file mode 100644 index 00000000..0c6fa90e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd3dd4871efd10e46947cb61c13797fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs new file mode 100644 index 00000000..d72d0f72 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs @@ -0,0 +1,53 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Link +{ + public class LinkXmlWriter + { + public void Write(string outputLinkXmlFile, HashSet refTypes) + { + string parentDir = Directory.GetParent(outputLinkXmlFile).FullName; + Directory.CreateDirectory(parentDir); + var writer = System.Xml.XmlWriter.Create(outputLinkXmlFile, + new System.Xml.XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true}); + + writer.WriteStartDocument(); + writer.WriteStartElement("linker"); + + var typesByAssembly = refTypes.GroupBy(t => t.DefinitionAssembly.Name.String).ToList(); + typesByAssembly.Sort((a, b) => String.Compare(a.Key, b.Key, StringComparison.Ordinal)); + + foreach(var assembly in typesByAssembly) + { + writer.WriteStartElement("assembly"); + writer.WriteAttributeString("fullname", assembly.Key); + List assTypeNames = assembly.Select(t => t.FullName).ToList(); + assTypeNames.Sort(string.CompareOrdinal); + foreach(var typeName in assTypeNames) + { +#if UNITY_2023_1_OR_NEWER + if (typeName == "UnityEngine.Debug") + { + continue; + } +#endif + writer.WriteStartElement("type"); + writer.WriteAttributeString("fullname", typeName); + writer.WriteAttributeString("preserve", "all"); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteEndDocument(); + writer.Close(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta new file mode 100644 index 00000000..4c6c61c7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5cc4ae4adc319b4bb1e115567d7613e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta new file mode 100644 index 00000000..3291c30d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3787c7d8b775c754aa4ae06bf78e96ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs new file mode 100644 index 00000000..7fed9300 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class AssemblyCache : AssemblyCacheBase + { + + public AssemblyCache(IAssemblyResolver assemblyResolver) : base(assemblyResolver) + { + + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta new file mode 100644 index 00000000..a99c3b2d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa4650e79a52228488aa85e0690ca52c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs new file mode 100644 index 00000000..04f2d15e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs @@ -0,0 +1,99 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public abstract class AssemblyCacheBase + { + private readonly IAssemblyResolver _assemblyPathResolver; + private readonly ModuleContext _modCtx; + private readonly AssemblyResolver _asmResolver; + private bool _loadedNetstandard; + + + public ModuleContext ModCtx => _modCtx; + + public Dictionary LoadedModules { get; } = new Dictionary(); + + private readonly List _loadedModulesIncludeNetstandard = new List(); + + protected AssemblyCacheBase(IAssemblyResolver assemblyResolver) + { + _assemblyPathResolver = assemblyResolver; + _modCtx = ModuleDef.CreateModuleContext(); + _asmResolver = (AssemblyResolver)_modCtx.AssemblyResolver; + _asmResolver.EnableTypeDefCache = true; + _asmResolver.UseGAC = false; + } + + + public ModuleDefMD TryLoadModule(string moduleName, bool loadReferenceAssemblies = true) + { + string dllPath = _assemblyPathResolver.ResolveAssembly(moduleName, false); + if (string.IsNullOrEmpty(dllPath)) + { + return null; + } + return LoadModule(moduleName, loadReferenceAssemblies); + } + + public ModuleDefMD LoadModule(string moduleName, bool loadReferenceAssemblies = true) + { + // Debug.Log($"load module:{moduleName}"); + if (LoadedModules.TryGetValue(moduleName, out var mod)) + { + return mod; + } + if (moduleName == "netstandard") + { + if (!_loadedNetstandard) + { + LoadNetStandard(); + } + return null; + } + mod = DoLoadModule(_assemblyPathResolver.ResolveAssembly(moduleName, true)); + LoadedModules.Add(moduleName, mod); + + if (loadReferenceAssemblies) + { + foreach (var refAsm in mod.GetAssemblyRefs()) + { + LoadModule(refAsm.Name); + } + } + + return mod; + } + + private void LoadNetStandard() + { + string netstandardDllPath = _assemblyPathResolver.ResolveAssembly("netstandard", false); + if (!string.IsNullOrEmpty(netstandardDllPath)) + { + DoLoadModule(netstandardDllPath); + } + else + { + DoLoadModule(MetaUtil.ResolveNetStandardAssemblyPath("netstandard2.0")); + DoLoadModule(MetaUtil.ResolveNetStandardAssemblyPath("netstandard2.1")); + } + _loadedNetstandard = true; + } + + private ModuleDefMD DoLoadModule(string dllPath) + { + //Debug.Log($"do load module:{dllPath}"); + ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(dllPath), _modCtx); + mod.EnableTypeDefFindCache = true; + _asmResolver.AddToCache(mod); + _loadedModulesIncludeNetstandard.Add(mod); + return mod; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta new file mode 100644 index 00000000..5c02171e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b01fa99119e72141bfee5628c0ffce1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs new file mode 100644 index 00000000..44c1c71f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs @@ -0,0 +1,50 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class AssemblyReferenceDeepCollector : AssemblyCacheBase + { + private readonly List _rootAssemblies; + + public IReadOnlyList GetRootAssemblyNames() + { + return _rootAssemblies; + } + + public List GetLoadedModulesExcludeRootAssemblies() + { + return LoadedModules.Where(e => !_rootAssemblies.Contains(e.Key)).Select(e => e.Value).ToList(); + } + + public List GetLoadedModules() + { + return LoadedModules.Select(e => e.Value).ToList(); + } + + public List GetLoadedModulesOfRootAssemblies() + { + return _rootAssemblies.Select(ass => LoadedModules[ass]).ToList(); + } + + public AssemblyReferenceDeepCollector(IAssemblyResolver assemblyResolver, List rootAssemblies) : base(assemblyResolver) + { + _rootAssemblies = rootAssemblies; + LoadAllAssembiles(); + } + + private void LoadAllAssembiles() + { + foreach (var asm in _rootAssemblies) + { + LoadModule(asm); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta new file mode 100644 index 00000000..e93354f6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0342c7d8575fdea49896260c77285286 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs new file mode 100644 index 00000000..973619a2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public abstract class AssemblyResolverBase : IAssemblyResolver + { + public string ResolveAssembly(string assemblyName, bool throwExIfNotFind) + { + if (TryResolveAssembly(assemblyName, out string assemblyPath)) + { + return assemblyPath; + } + if (throwExIfNotFind) + { + if (SettingsUtil.HotUpdateAssemblyNamesIncludePreserved.Contains(assemblyName)) + { + throw new Exception($"resolve Hot update dll:{assemblyName} failed! Please make sure that this hot update dll exists or the search path is configured in the external hot update path."); + } + else + { + throw new Exception($"resolve AOT dll:{assemblyName} failed! Please make sure that the AOT project has referenced the dll and generated the trimmed AOT dll correctly."); + } + } + return null; + } + + protected abstract bool TryResolveAssembly(string assemblyName, out string assemblyPath); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta new file mode 100644 index 00000000..e153d4e3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f8d48774b790364cbd36f1f68fd6614 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs new file mode 100644 index 00000000..3784b37b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + + public class AssemblySorter + { + class Node + { + public string Name; + public List Dependencies = new List(); + + public Node(string name) + { + Name = name; + } + } + + class TopologicalSorter + { + + public static List Sort(List nodes) + { + List sorted = new List(); + HashSet visited = new HashSet(); + HashSet tempMarks = new HashSet(); + + foreach (var node in nodes) + { + if (!visited.Contains(node)) + { + Visit(node, visited, tempMarks, sorted); + } + } + return sorted; + } + + private static void Visit(Node node, HashSet visited, HashSet tempMarks, List sorted) + { + if (tempMarks.Contains(node)) + { + throw new Exception("Detected cyclic dependency!"); + } + + if (!visited.Contains(node)) + { + tempMarks.Add(node); + foreach (var dependency in node.Dependencies) + { + Visit(dependency, visited, tempMarks, sorted); + } + tempMarks.Remove(node); + visited.Add(node); + sorted.Add(node); + } + } + } + + private static List SortAssemblyByReferenceOrder(IEnumerable assemblies, Dictionary> refs) + { + var nodes = new List(); + var nodeMap = new Dictionary(); + foreach (var assembly in assemblies) + { + var node = new Node(assembly); + nodes.Add(node); + nodeMap.Add(assembly, node); + } + foreach (var assembly in assemblies) + { + var node = nodeMap[assembly]; + foreach (var refAssembly in refs[assembly]) + { + node.Dependencies.Add(nodeMap[refAssembly]); + } + } + var sortedNodes = TopologicalSorter.Sort(nodes); + return sortedNodes.Select(node => node.Name).ToList(); + } + + public static List SortAssemblyByReferenceOrder(IEnumerable assemblies, IAssemblyResolver assemblyResolver) + { + var assCache = new AssemblyCache(assemblyResolver); + var assRefAssemblies = new Dictionary>(); + foreach (var assName in assemblies) + { + var refAssemblies = new HashSet(); + var mod = assCache.LoadModule(assName, false); + foreach (var refAss in mod.GetAssemblyRefs()) + { + if (assemblies.Contains(refAss.Name.ToString())) + { + refAssemblies.Add(refAss.Name.ToString()); + } + } + assRefAssemblies.Add(assName, refAssemblies); + } + return SortAssemblyByReferenceOrder(assemblies, assRefAssemblies); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs.meta new file mode 100644 index 00000000..6fce7f93 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblySorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9b8eb45398fa344daa8c6e9b9fbf291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs new file mode 100644 index 00000000..31a0c517 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public class CombinedAssemblyResolver : AssemblyResolverBase + { + private readonly IAssemblyResolver[] _resolvers; + + public CombinedAssemblyResolver(params IAssemblyResolver[] resolvers) + { + _resolvers = resolvers; + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + foreach(var resolver in _resolvers) + { + var assembly = resolver.ResolveAssembly(assemblyName, false); + if (assembly != null) + { + assemblyPath = assembly; + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta new file mode 100644 index 00000000..81b443c1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89b83906438c52d4b9af4aaef055f177 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs new file mode 100644 index 00000000..82065537 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class FixedSetAssemblyResolver : AssemblyResolverBase + { + private readonly string _rootDir; + private readonly HashSet _fileNames; + + public FixedSetAssemblyResolver(string rootDir, IEnumerable fileNameNotExts) + { + _rootDir = rootDir; + _fileNames = new HashSet(fileNameNotExts); + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + if (_fileNames.Contains(assemblyName)) + { + assemblyPath = $"{_rootDir}/{assemblyName}.dll"; + if (File.Exists(assemblyPath)) + { + Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}"); + return true; + } + assemblyPath = $"{_rootDir}/{assemblyName}.dll.bytes"; + if (File.Exists(assemblyPath)) + { + Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}"); + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta new file mode 100644 index 00000000..cc4fb61a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f135accd10f42c64b9735c3aa8cb1e77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs new file mode 100644 index 00000000..d2003993 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs @@ -0,0 +1,110 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + + public class GenericArgumentContext + { + private readonly List typeArgsStack; + private readonly List methodArgsStack; + + public GenericArgumentContext(List typeArgsStack, List methodArgsStack) + { + this.typeArgsStack = typeArgsStack; + this.methodArgsStack = methodArgsStack; + } + + public TypeSig Resolve(TypeSig typeSig) + { + if (!typeSig.ContainsGenericParameter) + { + return typeSig; + } + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.ByRef: return new ByRefSig(Resolve(typeSig.Next)); + + case ElementType.SZArray: return new SZArraySig(Resolve(typeSig.Next)); + case ElementType.Array: + { + var ara = (ArraySig)typeSig; + return new ArraySig(Resolve(typeSig.Next), ara.Rank, ara.Sizes, ara.LowerBounds); + } + + case ElementType.Var: + { + GenericVar genericVar = (GenericVar)typeSig; + var newSig = Resolve(typeArgsStack, genericVar.Number); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + + case ElementType.MVar: + { + GenericMVar genericVar = (GenericMVar)typeSig; + var newSig = Resolve(methodArgsStack, genericVar.Number); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + case ElementType.GenericInst: + { + var gia = (GenericInstSig)typeSig; + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => Resolve(ga)).ToList()); + } + + case ElementType.FnPtr: + { + var fptr = (FnPtrSig)typeSig; + var cs = fptr.Signature; + CallingConventionSig ccs; + switch (cs) + { + case MethodSig ms: + { + ccs = new MethodSig(ms.GetCallingConvention(), ms.GenParamCount, Resolve(ms.RetType), ms.Params.Select(p => Resolve(p)).ToList()); + break; + } + case PropertySig ps: + { + ccs = new PropertySig(ps.HasThis, Resolve(ps.RetType)); + break; + } + case GenericInstMethodSig gims: + { + ccs = new GenericInstMethodSig(gims.GenericArguments.Select(ga => Resolve(ga)).ToArray()); + break; + } + default: throw new NotSupportedException(cs.ToString()); + } + return new FnPtrSig(ccs); + } + + case ElementType.ValueArray: + { + var vas = (ValueArraySig)typeSig; + return new ValueArraySig(Resolve(vas.Next), vas.Size); + } + default: return typeSig; + } + } + + private TypeSig Resolve(List args, uint number) + { + return args[(int)number]; + } + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta new file mode 100644 index 00000000..acb9355b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07595a9b5b2f54c44a67022ae3e077d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs new file mode 100644 index 00000000..0479f6f9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs @@ -0,0 +1,74 @@ +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class GenericClass + { + public TypeDef Type { get; } + + public List KlassInst { get; } + + private readonly int _hashCode; + + public GenericClass(TypeDef type, List classInst) + { + Type = type; + KlassInst = classInst; + _hashCode = ComputHashCode(); + } + + public GenericClass ToGenericShare() + { + return new GenericClass(Type, MetaUtil.ToShareTypeSigs(Type.Module.CorLibTypes, KlassInst)); + } + + public override bool Equals(object obj) + { + if (obj is GenericClass gc) + { + return Type == gc.Type && MetaUtil.EqualsTypeSigArray(KlassInst, gc.KlassInst); + } + return false; + } + + public override int GetHashCode() + { + return _hashCode; + } + + private int ComputHashCode() + { + int hash = TypeEqualityComparer.Instance.GetHashCode(Type); + if (KlassInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(KlassInst)); + } + return hash; + } + + public TypeSig ToTypeSig() + { + return new GenericInstSig(this.Type.ToTypeSig().ToClassOrValueTypeSig(), this.KlassInst); + } + + public static GenericClass ResolveClass(TypeSpec type, GenericArgumentContext ctx) + { + var sig = type.TypeSig.ToGenericInstSig(); + if (sig == null) + { + return null; + } + TypeDef def = type.ResolveTypeDef(); + if (def == null) + { + Debug.LogWarning($"type:{type} ResolveTypeDef() == null"); + return null; + } + var klassInst = ctx != null ? sig.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() : sig.GenericArguments.ToList(); + return new GenericClass(def, klassInst); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta new file mode 100644 index 00000000..438a1a3d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c95ff173013909548bd9e2008812f9ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs new file mode 100644 index 00000000..243aecb9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs @@ -0,0 +1,109 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class GenericMethod + { + public MethodDef Method { get; } + + public List KlassInst { get; } + + public List MethodInst { get; } + + private readonly int _hashCode; + + public GenericMethod(MethodDef method, List classInst, List methodInst) + { + Method = method; + KlassInst = classInst; + MethodInst = methodInst; + _hashCode = ComputHashCode(); + } + + public GenericMethod ToGenericShare() + { + ICorLibTypes corLibTypes = Method.Module.CorLibTypes; + return new GenericMethod(Method, MetaUtil.ToShareTypeSigs(corLibTypes, KlassInst), MetaUtil.ToShareTypeSigs(corLibTypes, MethodInst)); + } + + public override bool Equals(object obj) + { + GenericMethod o = (GenericMethod)obj; + return Method == o.Method + && MetaUtil.EqualsTypeSigArray(KlassInst, o.KlassInst) + && MetaUtil.EqualsTypeSigArray(MethodInst, o.MethodInst); + } + + public override int GetHashCode() + { + return _hashCode; + } + + public override string ToString() + { + return $"{Method}|{string.Join(",", (IEnumerable)KlassInst ?? Array.Empty())}|{string.Join(",", (IEnumerable)MethodInst ?? Array.Empty())}"; + } + + private int ComputHashCode() + { + int hash = MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(Method); + if (KlassInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(KlassInst)); + } + if (MethodInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(MethodInst)); + } + return hash; + } + + public MethodSpec ToMethodSpec() + { + IMethodDefOrRef mt = KlassInst != null ? + (IMethodDefOrRef)new MemberRefUser(this.Method.Module, Method.Name, Method.MethodSig, new TypeSpecUser(new GenericInstSig(this.Method.DeclaringType.ToTypeSig().ToClassOrValueTypeSig(), this.KlassInst))) + : this.Method; + return new MethodSpecUser(mt, new GenericInstMethodSig(MethodInst)); + } + + public static GenericMethod ResolveMethod(IMethod method, GenericArgumentContext ctx) + { + //Debug.Log($"== resolve method:{method}"); + TypeDef typeDef = null; + List klassInst = null; + List methodInst = null; + + MethodDef methodDef = null; + + + var decalringType = method.DeclaringType; + typeDef = decalringType.ResolveTypeDef(); + if (typeDef == null) + { + return null; + } + GenericInstSig gis = decalringType.TryGetGenericInstSig(); + if (gis != null) + { + klassInst = ctx != null ? gis.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() : gis.GenericArguments.ToList(); + } + methodDef = method.ResolveMethodDef(); + if (methodDef == null) + { + //Debug.LogWarning($"method:{method} ResolveMethodDef() == null"); + return null; + } + if (method is MethodSpec methodSpec) + { + methodInst = ctx != null ? methodSpec.GenericInstMethodSig.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() + : methodSpec.GenericInstMethodSig.GenericArguments.ToList(); + } + return new GenericMethod(methodDef, klassInst, methodInst); + } + + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta new file mode 100644 index 00000000..ef15a5c5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88ecf3d52ec393b4cac142518944e487 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs new file mode 100644 index 00000000..25a6d422 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public interface IAssemblyResolver + { + string ResolveAssembly(string assemblyName, bool throwExIfNotFind); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta new file mode 100644 index 00000000..d56059d5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f962a018018dbb945a19f82d2e098686 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs new file mode 100644 index 00000000..23025404 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs @@ -0,0 +1,217 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; + +namespace HybridCLR.Editor.Meta +{ + public static class MetaUtil + { + + public static bool EqualsTypeSig(TypeSig a, TypeSig b) + { + if (a == b) + { + return true; + } + if (a != null && b != null) + { + return TypeEqualityComparer.Instance.Equals(a, b); + } + return false; + } + + public static bool EqualsTypeSigArray(List a, List b) + { + if (a == b) + { + return true; + } + if (a != null && b != null) + { + if (a.Count != b.Count) + { + return false; + } + for (int i = 0; i < a.Count; i++) + { + if (!TypeEqualityComparer.Instance.Equals(a[i], b[i])) + { + return false; + } + } + return true; + } + return false; + } + + public static TypeSig Inflate(TypeSig sig, GenericArgumentContext ctx) + { + if (!sig.ContainsGenericParameter) + { + return sig; + } + return ctx.Resolve(sig); + } + + public static TypeSig ToShareTypeSig(ICorLibTypes corTypes, TypeSig typeSig) + { + var a = typeSig.RemovePinnedAndModifiers(); + switch (a.ElementType) + { + case ElementType.Void: return corTypes.Void; + case ElementType.Boolean: return corTypes.Byte; + case ElementType.Char: return corTypes.UInt16; + case ElementType.I1: return corTypes.SByte; + case ElementType.U1:return corTypes.Byte; + case ElementType.I2: return corTypes.Int16; + case ElementType.U2: return corTypes.UInt16; + case ElementType.I4: return corTypes.Int32; + case ElementType.U4: return corTypes.UInt32; + case ElementType.I8: return corTypes.Int64; + case ElementType.U8: return corTypes.UInt64; + case ElementType.R4: return corTypes.Single; + case ElementType.R8: return corTypes.Double; + case ElementType.String: return corTypes.Object; + case ElementType.TypedByRef: return corTypes.TypedReference; + case ElementType.I: return corTypes.IntPtr; + case ElementType.U: return corTypes.UIntPtr; + case ElementType.Object: return corTypes.Object; + case ElementType.Sentinel: return typeSig; + case ElementType.Ptr: return corTypes.UIntPtr; + case ElementType.ByRef: return corTypes.UIntPtr; + case ElementType.SZArray: return corTypes.Object; + case ElementType.Array: return corTypes.Object; + case ElementType.ValueType: + { + TypeDef typeDef = a.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + return typeSig; + } + case ElementType.Var: + case ElementType.MVar: + case ElementType.Class: return corTypes.Object; + case ElementType.GenericInst: + { + var gia = (GenericInstSig)a; + TypeDef typeDef = gia.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + if (!typeDef.IsValueType) + { + return corTypes.Object; + } + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList()); + } + case ElementType.FnPtr: return corTypes.UIntPtr; + case ElementType.ValueArray: return typeSig; + case ElementType.Module: return typeSig; + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + public static List ToShareTypeSigs(ICorLibTypes corTypes, IList typeSigs) + { + if (typeSigs == null) + { + return null; + } + return typeSigs.Select(s => ToShareTypeSig(corTypes, s)).ToList(); + } + + public static IAssemblyResolver CreateHotUpdateAssemblyResolver(BuildTarget target, List hotUpdateDlls) + { + var externalDirs = HybridCLRSettings.Instance.externalHotUpdateAssembliyDirs; + var defaultHotUpdateOutputDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target); + IAssemblyResolver defaultHotUpdateResolver = new FixedSetAssemblyResolver(defaultHotUpdateOutputDir, hotUpdateDlls); + if (externalDirs == null || externalDirs.Length == 0) + { + return defaultHotUpdateResolver; + } + else + { + var resolvers = new List(); + foreach (var dir in externalDirs) + { + resolvers.Add(new FixedSetAssemblyResolver($"{dir}/{target}", hotUpdateDlls)); + resolvers.Add(new FixedSetAssemblyResolver(dir, hotUpdateDlls)); + } + resolvers.Add(defaultHotUpdateResolver); + return new CombinedAssemblyResolver(resolvers.ToArray()); + } + } + + public static IAssemblyResolver CreateAOTAssemblyResolver(BuildTarget target) + { + return new PathAssemblyResolver(SettingsUtil.GetAssembliesPostIl2CppStripDir(target)); + } + + public static IAssemblyResolver CreateHotUpdateAndAOTAssemblyResolver(BuildTarget target, List hotUpdateDlls) + { + return new CombinedAssemblyResolver( + CreateHotUpdateAssemblyResolver(target, hotUpdateDlls), + CreateAOTAssemblyResolver(target) + ); + } + + public static string ResolveNetStandardAssemblyPath(string assemblyName) + { + return $"{SettingsUtil.HybridCLRDataPathInPackage}/NetStandard/{assemblyName}.dll"; + } + + + public static List CreateDefaultGenericParams(ModuleDef module, int genericParamCount) + { + var methodGenericParams = new List(); + for (int i = 0; i < genericParamCount; i++) + { + methodGenericParams.Add(module.CorLibTypes.Object); + } + return methodGenericParams; + } + + public static bool IsSupportedPInvokeTypeSig(TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + if (typeSig.IsByRef) + { + return true; + } + switch (typeSig.ElementType) + { + case ElementType.SZArray: + case ElementType.Array: + //case ElementType.Class: + case ElementType.String: + //case ElementType.Object: + return false; + default: return true; + } + } + + public static bool IsSupportedPInvokeMethodSignature(MethodSig methodSig) + { + return IsSupportedPInvokeTypeSig(methodSig.RetType) && methodSig.Params.All(p => IsSupportedPInvokeTypeSig(p)); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta new file mode 100644 index 00000000..d9342a4e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3dbfe2e8b6a92742b18e287c5d281dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs new file mode 100644 index 00000000..bb36692d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs @@ -0,0 +1,79 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public class MethodReferenceAnalyzer + { + private readonly Action, List, GenericMethod> _onNewMethod; + + private readonly ConcurrentDictionary> _methodEffectInsts = new ConcurrentDictionary>(); + + public MethodReferenceAnalyzer(Action, List, GenericMethod> onNewMethod) + { + _onNewMethod = onNewMethod; + } + + public void WalkMethod(MethodDef method, List klassGenericInst, List methodGenericInst) + { + var ctx = new GenericArgumentContext(klassGenericInst, methodGenericInst); + + if (_methodEffectInsts.TryGetValue(method, out var effectInsts)) + { + foreach (var met in effectInsts) + { + var resolveMet = GenericMethod.ResolveMethod(met, ctx)?.ToGenericShare(); + _onNewMethod(method, klassGenericInst, methodGenericInst, resolveMet); + } + return; + } + + var body = method.Body; + if (body == null || !body.HasInstructions) + { + return; + } + + effectInsts = new List(); + foreach (var inst in body.Instructions) + { + if (inst.Operand == null) + { + continue; + } + switch (inst.Operand) + { + case IMethod met: + { + if (!met.IsMethod) + { + continue; + } + var resolveMet = GenericMethod.ResolveMethod(met, ctx)?.ToGenericShare(); + if (resolveMet == null) + { + continue; + } + effectInsts.Add(met); + _onNewMethod(method, klassGenericInst, methodGenericInst, resolveMet); + break; + } + case ITokenOperand token: + { + //GenericParamContext paramContext = method.HasGenericParameters || method.DeclaringType.HasGenericParameters ? + // new GenericParamContext(method.DeclaringType, method) : default; + //method.Module.ResolveToken(token.MDToken, paramContext); + break; + } + } + } + _methodEffectInsts.TryAdd(method, effectInsts); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta new file mode 100644 index 00000000..7554a583 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c644b0c018fb87498d69c3202439d21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs new file mode 100644 index 00000000..1cfc3766 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class PathAssemblyResolver : AssemblyResolverBase + { + private readonly string[] _searchPaths; + public PathAssemblyResolver(params string[] searchPaths) + { + _searchPaths = searchPaths; + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + foreach(var path in _searchPaths) + { + assemblyPath = Path.Combine(path, $"{assemblyName}.dll"); + if (File.Exists(assemblyPath)) + { + Debug.Log($"resolve {assemblyName} at {assemblyPath}"); + return true; + } + assemblyPath = Path.Combine(path, $"{assemblyName}.dll.bytes"); + if (File.Exists(assemblyPath)) + { + Debug.Log($"resolve {assemblyName} at {assemblyPath}"); + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta new file mode 100644 index 00000000..af02cb81 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 121d574bf01969444aa6619a8f6dbb4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta new file mode 100644 index 00000000..5ff8e805 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c2444f09010bce41a52d951b7100c49 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs new file mode 100644 index 00000000..dd0fd66d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs @@ -0,0 +1,205 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.MethodBridge +{ + + public class Analyzer + { + public class Options + { + public AssemblyReferenceDeepCollector Collector { get; set; } + + public int MaxIterationCount { get; set; } + } + + private readonly int _maxInterationCount; + private readonly AssemblyReferenceDeepCollector _assemblyCollector; + + private readonly object _lock = new object(); + + private readonly List _typeDefs = new List(); + + private readonly HashSet _genericTypes = new HashSet(); + private readonly HashSet _genericMethods = new HashSet(); + + private List _processingMethods = new List(); + private List _newMethods = new List(); + + public IReadOnlyList TypeDefs => _typeDefs; + + public IReadOnlyCollection GenericTypes => _genericTypes; + + public IReadOnlyCollection GenericMethods => _genericMethods; + + + private readonly MethodReferenceAnalyzer _methodReferenceAnalyzer; + + public Analyzer(Options options) + { + _maxInterationCount = options.MaxIterationCount; + _assemblyCollector = options.Collector; + _methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod); + } + + private void TryAddAndWalkGenericType(GenericClass gc) + { + if (gc == null) + { + return; + } + lock(_lock) + { + gc = StandardizeClass(gc); + if (_genericTypes.Add(gc)) + { + WalkType(gc); + } + } + } + + private GenericClass StandardizeClass(GenericClass gc) + { + TypeDef typeDef = gc.Type; + ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + List klassGenericParams = gc.KlassInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gc.KlassInst) : (typeDef.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, typeDef.GenericParameters.Count) : null); + return new GenericClass(typeDef, klassGenericParams); + } + + private GenericMethod StandardizeMethod(GenericMethod gm) + { + TypeDef typeDef = gm.Method.DeclaringType; + ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + List klassGenericParams = gm.KlassInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gm.KlassInst) : (typeDef.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, typeDef.GenericParameters.Count) : null); + List methodGenericParams = gm.MethodInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gm.MethodInst) : (gm.Method.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, gm.Method.GenericParameters.Count) : null); + return new GenericMethod(gm.Method, klassGenericParams, methodGenericParams); + } + + private void OnNewMethod(MethodDef methodDef, List klassGenericInst, List methodGenericInst, GenericMethod method) + { + lock(_lock) + { + method = StandardizeMethod(method); + if (_genericMethods.Add(method)) + { + _newMethods.Add(method); + } + if (method.KlassInst != null) + { + TryAddAndWalkGenericType(new GenericClass(method.Method.DeclaringType, method.KlassInst)); + } + } + } + + private void WalkType(GenericClass gc) + { + //Debug.Log($"typespec:{sig} {sig.GenericType} {sig.GenericType.TypeDefOrRef.ResolveTypeDef()}"); + //Debug.Log($"== walk generic type:{new GenericInstSig(gc.Type.ToTypeSig().ToClassOrValueTypeSig(), gc.KlassInst)}"); + ITypeDefOrRef baseType = gc.Type.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass parentType = GenericClass.ResolveClass((TypeSpec)baseType, new GenericArgumentContext(gc.KlassInst, null)); + TryAddAndWalkGenericType(parentType); + } + foreach (var method in gc.Type.Methods) + { + var gm = StandardizeMethod(new GenericMethod(method, gc.KlassInst, null)); + //Debug.Log($"add method:{gm.Method} {gm.KlassInst}"); + + if (_genericMethods.Add(gm)) + { + if (method.HasBody && method.Body.Instructions != null) + { + _newMethods.Add(gm); + } + } + } + } + + private void WalkType(TypeDef typeDef) + { + _typeDefs.Add(typeDef); + ITypeDefOrRef baseType = typeDef.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass gc = GenericClass.ResolveClass((TypeSpec)baseType, null); + TryAddAndWalkGenericType(gc); + } + foreach (var method in typeDef.Methods) + { + // 对于带泛型的参数,统一泛型共享为object + var gm = StandardizeMethod(new GenericMethod(method, null, null)); + _genericMethods.Add(gm); + } + } + + private void Prepare() + { + // 将所有非泛型函数全部加入函数列表,同时立马walk这些method。 + // 后续迭代中将只遍历MethodSpec + foreach (var ass in _assemblyCollector.GetLoadedModules()) + { + foreach (TypeDef typeDef in ass.GetTypes()) + { + WalkType(typeDef); + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.TypeSpecTable.Rows; rid <= n; rid++) + { + var ts = ass.ResolveTypeSpec(rid); + var cs = GenericClass.ResolveClass(ts, null); + if (cs != null) + { + TryAddAndWalkGenericType(cs); + } + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.MethodSpecTable.Rows; rid <= n; rid++) + { + var ms = ass.ResolveMethodSpec(rid); + var gm = GenericMethod.ResolveMethod(ms, null)?.ToGenericShare(); + if (gm == null) + { + continue; + } + gm = StandardizeMethod(gm); + if (_genericMethods.Add(gm)) + { + _newMethods.Add(gm); + } + } + } + Debug.Log($"PostPrepare allMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + + private void RecursiveCollect() + { + for (int i = 0; i < _maxInterationCount && _newMethods.Count > 0; i++) + { + var temp = _processingMethods; + _processingMethods = _newMethods; + _newMethods = temp; + _newMethods.Clear(); + + Task.WaitAll(_processingMethods.Select(method => Task.Run(() => + { + _methodReferenceAnalyzer.WalkMethod(method.Method, method.KlassInst, method.MethodInst); + })).ToArray()); + Debug.Log($"iteration:[{i}] genericClass:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + } + + public void Run() + { + Prepare(); + RecursiveCollect(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta new file mode 100644 index 00000000..4fd796f4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee1ec106190e514489c7ba32bc7bc2e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs new file mode 100644 index 00000000..3459e15d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs @@ -0,0 +1,9 @@ +using dnlib.DotNet; + +namespace HybridCLR.Editor.MethodBridge +{ + public class CallNativeMethodSignatureInfo + { + public MethodSig MethodSig { get; set; } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs.meta new file mode 100644 index 00000000..026237dd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CallNativeMethodSignatureInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2e4ca0a49975a84a8a72dbc70ec7795 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs new file mode 100644 index 00000000..14611ac2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs @@ -0,0 +1,65 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.MethodBridge +{ + + public class CalliAnalyzer + { + private readonly List _rootModules = new List(); + + private readonly List _calliMethodSignatures = new List(); + + public List CalliMethodSignatures => _calliMethodSignatures; + + public CalliAnalyzer(AssemblyCache cache, List assemblyNames) + { + foreach (var assemblyName in assemblyNames) + { + _rootModules.Add(cache.LoadModule(assemblyName)); + } + } + + private void CollectCalli() + { + foreach (var mod in _rootModules) + { + Debug.Log($"ass:{mod.FullName} methodcount:{mod.Metadata.TablesStream.MethodTable.Rows}"); + for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++) + { + var method = mod.ResolveMethod(rid); + //Debug.Log($"method:{method}"); + if (!method.HasBody) + { + continue; + } + + foreach (var il in method.Body.Instructions) + { + if (il.OpCode.Code == dnlib.DotNet.Emit.Code.Calli) + { + MethodSig methodSig = (MethodSig)il.Operand; + + _calliMethodSignatures.Add(new CallNativeMethodSignatureInfo() + { + MethodSig = methodSig, + }); + Debug.Log($"method:{method} calli method signature:{methodSig}"); + } + } + } + } + } + + public void Run() + { + CollectCalli(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs.meta new file mode 100644 index 00000000..620c7c7f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/CalliAnalyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ce33c8e48da5a649b261ba3a60fd3b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs new file mode 100644 index 00000000..211dcbdb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs @@ -0,0 +1,1288 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.Template; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using TypeInfo = HybridCLR.Editor.ABI.TypeInfo; +using CallingConvention = System.Runtime.InteropServices.CallingConvention; +using TypeAttributes = dnlib.DotNet.TypeAttributes; +using System.Runtime.InteropServices; + +namespace HybridCLR.Editor.MethodBridge +{ + public class Generator + { + public class Options + { + public string TemplateCode { get; set; } + + public string OutputFile { get; set; } + + public IReadOnlyCollection GenericMethods { get; set; } + + public List ReversePInvokeMethods { get; set; } + + public IReadOnlyCollection CalliMethodSignatures { get; set; } + + public bool Development { get; set; } + } + + private class ABIReversePInvokeMethodInfo + { + public MethodDesc Method { get; set; } + + public CallingConvention Callvention { get; set; } + + public int Count { get; set; } + + public string Signature { get; set; } + } + + private class CalliMethodInfo + { + public MethodDesc Method { get; set; } + + public CallingConvention Callvention { get; set; } + + public string Signature { get; set; } + } + + private readonly List _genericMethods; + + private readonly List _originalReversePInvokeMethods; + + private readonly List _originalCalliMethodSignatures; + + private readonly string _templateCode; + + private readonly string _outputFile; + + private readonly bool _development; + + private readonly TypeCreator _typeCreator; + + private readonly HashSet _managed2nativeMethodSet = new HashSet(); + + private readonly HashSet _native2managedMethodSet = new HashSet(); + + private readonly HashSet _adjustThunkMethodSet = new HashSet(); + + private List _reversePInvokeMethods; + + private List _callidMethods; + + public Generator(Options options) + { + List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList(); + genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2)); + _genericMethods = genericMethodInfo.Select(m => m.Item1).ToList(); + _originalReversePInvokeMethods = options.ReversePInvokeMethods; + _originalCalliMethodSignatures = options.CalliMethodSignatures.ToList(); + + _templateCode = options.TemplateCode; + _outputFile = options.OutputFile; + _typeCreator = new TypeCreator(); + _development = options.Development; + } + + private readonly Dictionary _sig2Types = new Dictionary(); + + private TypeInfo GetSharedTypeInfo(TypeSig type) + { + var typeInfo = _typeCreator.CreateTypeInfo(type); + if (!typeInfo.IsStruct) + { + return typeInfo; + } + string sigName = ToFullName(typeInfo.Klass); + if (!_sig2Types.TryGetValue(sigName, out var sharedTypeInfo)) + { + sharedTypeInfo = typeInfo; + _sig2Types.Add(sigName, sharedTypeInfo); + } + return sharedTypeInfo; + } + + private MethodDesc CreateMethodDesc(MethodDef methodDef, bool forceRemoveThis, TypeSig returnType, List parameters) + { + var paramInfos = new List(); + if (forceRemoveThis && !methodDef.IsStatic) + { + parameters.RemoveAt(0); + } + if (returnType.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters"); + } + foreach (var paramInfo in parameters) + { + if (paramInfo.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters"); + } + paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) }); + } + var mbs = new MethodDesc() + { + MethodDef = methodDef, + ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void }, + ParamInfos = paramInfos, + }; + return mbs; + } + + private MethodDesc CreateMethodDesc(TypeSig returnType, List parameters) + { + var paramInfos = new List(); + if (returnType.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method has generic parameters"); + } + foreach (var paramInfo in parameters) + { + if (paramInfo.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method has generic parameters"); + } + paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) }); + } + var mbs = new MethodDesc() + { + MethodDef = null, + ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void }, + ParamInfos = paramInfos, + }; + return mbs; + } + + private void AddManaged2NativeMethod(MethodDesc method) + { + method.Init(); + _managed2nativeMethodSet.Add(method); + } + + private void AddNative2ManagedMethod(MethodDesc method) + { + method.Init(); + _native2managedMethodSet.Add(method); + } + + private void AddAdjustThunkMethod(MethodDesc method) + { + method.Init(); + _adjustThunkMethodSet.Add(method); + } + + private void ProcessMethod(MethodDef method, List klassInst, List methodInst) + { + if (method.IsPrivate || (method.IsAssembly && !method.IsPublic && !method.IsFamily)) + { + if (klassInst == null && methodInst == null) + { + return; + } + else + { + //Debug.Log($"[PreservedMethod] method:{method}"); + } + } + ICorLibTypes corLibTypes = method.Module.CorLibTypes; + TypeSig returnType; + List parameters; + if (klassInst == null && methodInst == null) + { + if (method.HasGenericParameters) + { + throw new Exception($"[PreservedMethod] method:{method} has generic parameters"); + } + returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType); + parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList(); + } + else + { + var gc = new GenericArgumentContext(klassInst, methodInst); + returnType = MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(method.ReturnType, gc)); + parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(p.Type, gc))).ToList(); + } + + var m2nMethod = CreateMethodDesc(method, false, returnType, parameters); + AddManaged2NativeMethod(m2nMethod); + + if (method.IsVirtual) + { + if (method.DeclaringType.IsInterface) + { + AddAdjustThunkMethod(m2nMethod); + } + //var adjustThunkMethod = CreateMethodDesc(method, true, returnType, parameters); + AddNative2ManagedMethod(m2nMethod); + } + if (method.Name == "Invoke" && method.DeclaringType.IsDelegate) + { + var openMethod = CreateMethodDesc(method, true, returnType, parameters); + AddNative2ManagedMethod(openMethod); + } + } + + private void PrepareMethodBridges() + { + foreach (var method in _genericMethods) + { + ProcessMethod(method.Method, method.KlassInst, method.MethodInst); + } + foreach (var reversePInvokeMethod in _originalReversePInvokeMethods) + { + MethodDef method = reversePInvokeMethod.Method; + ICorLibTypes corLibTypes = method.Module.CorLibTypes; + + var returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType); + var parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList(); + var sharedMethod = CreateMethodDesc(method, true, returnType, parameters); + sharedMethod.Init(); + AddNative2ManagedMethod(sharedMethod); + } + } + + static void CheckUnique(IEnumerable names) + { + var set = new HashSet(); + foreach (var name in names) + { + if (!set.Add(name)) + { + throw new Exception($"[CheckUnique] duplicate name:{name}"); + } + } + } + + + private List _managed2NativeMethodList0; + private List _native2ManagedMethodList0; + private List _adjustThunkMethodList0; + + private List _structTypes0; + + private void CollectTypesAndMethods() + { + _managed2NativeMethodList0 = _managed2nativeMethodSet.ToList(); + _managed2NativeMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + _native2ManagedMethodList0 = _native2managedMethodSet.ToList(); + _native2ManagedMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + _adjustThunkMethodList0 = _adjustThunkMethodSet.ToList(); + _adjustThunkMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + + var structTypeSet = new HashSet(); + CollectStructDefs(_managed2NativeMethodList0, structTypeSet); + CollectStructDefs(_native2ManagedMethodList0, structTypeSet); + CollectStructDefs(_adjustThunkMethodList0, structTypeSet); + CollectStructDefs(_originalCalliMethodSignatures.Select(m => m.MethodSig).ToList(), structTypeSet); + _structTypes0 = structTypeSet.ToList(); + _structTypes0.Sort((a, b) => a.TypeId - b.TypeId); + + CheckUnique(_structTypes0.Select(t => ToFullName(t.Klass))); + CheckUnique(_structTypes0.Select(t => t.CreateSigName())); + + Debug.LogFormat("== before optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}", + _managed2NativeMethodList0.Count, _native2ManagedMethodList0.Count, _adjustThunkMethodList0.Count, _structTypes0.Count); + } + + private class AnalyzeFieldInfo + { + public FieldDef field; + + public TypeInfo type; + } + + private class AnalyzeTypeInfo + { + public TypeInfo isoType; + public List fields; + public string signature; + public uint originalPackingSize; + public uint packingSize; + public uint classSize; + public LayoutKind layout; + public bool blittable; + } + + private readonly Dictionary _analyzeTypeInfos = new Dictionary(); + + private readonly Dictionary _signature2Type = new Dictionary(); + + + + private bool IsBlittable(TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + if (typeSig.IsByRef) + { + return true; + } + switch (typeSig.ElementType) + { + case ElementType.Void: return false; + case ElementType.Boolean: + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.Char: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + case ElementType.I8: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.I: + case ElementType.U: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.FnPtr: + case ElementType.TypedByRef: return true; + case ElementType.String: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.Object: + case ElementType.Module: + case ElementType.Var: + case ElementType.MVar: return false; + case ElementType.ValueType: + { + TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{typeSig} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function"); + } + if (typeDef.IsEnum) + { + return true; + } + return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable; + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)typeSig; + if (!gis.GenericType.IsValueType) + { + return false; + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return true; + } + return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable; + } + default: throw new NotSupportedException($"{typeSig.ElementType}"); + } + } + + private AnalyzeTypeInfo CalculateAnalyzeTypeInfoBasic(TypeInfo typeInfo) + { + Debug.Assert(typeInfo.IsStruct); + if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati)) + { + return ati; + } + TypeSig type = typeInfo.Klass; + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + + List klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList(); + GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + var fields = new List(); + + bool blittable = true; + foreach (FieldDef field in typeDef.Fields) + { + if (field.IsStatic) + { + continue; + } + TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + blittable &= IsBlittable(fieldType); + TypeInfo sharedFieldTypeInfo = GetSharedTypeInfo(fieldType); + TypeInfo isoType = ToIsomorphicType(sharedFieldTypeInfo); + fields.Add(new AnalyzeFieldInfo { field = field, type = isoType }); + } + //analyzeTypeInfo.blittable = blittable; + //analyzeTypeInfo.packingSize = blittable ? analyzeTypeInfo.originalPackingSize : 0; + + ClassLayout sa = typeDef.ClassLayout; + uint originalPackingSize = sa?.PackingSize ?? 0; + var analyzeTypeInfo = new AnalyzeTypeInfo() + { + originalPackingSize = originalPackingSize, + packingSize = blittable && !typeDef.IsAutoLayout ? originalPackingSize : 0, + classSize = sa?.ClassSize ?? 0, + layout = typeDef.IsAutoLayout ? LayoutKind.Auto : (typeDef.IsExplicitLayout ? LayoutKind.Explicit : LayoutKind.Sequential), + fields = fields, + blittable = blittable, + }; + _analyzeTypeInfos.Add(typeInfo, analyzeTypeInfo); + analyzeTypeInfo.signature = GetOrCalculateTypeInfoSignature(typeInfo); + + if (_signature2Type.TryGetValue(analyzeTypeInfo.signature, out var sharedType)) + { + // Debug.Log($"[ToIsomorphicType] type:{type.Klass} ==> sharedType:{sharedType.Klass} signature:{signature} "); + analyzeTypeInfo.isoType = sharedType; + } + else + { + analyzeTypeInfo.isoType = typeInfo; + _signature2Type.Add(analyzeTypeInfo.signature, typeInfo); + } + + return analyzeTypeInfo; + } + + private string GetOrCalculateTypeInfoSignature(TypeInfo typeInfo) + { + if (!typeInfo.IsStruct) + { + return typeInfo.CreateSigName(); + } + + var ati = _analyzeTypeInfos[typeInfo]; + + //if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati)) + //{ + // return ati.signature; + //} + //ati = CalculateAnalyzeTypeInfoBasic(typeInfo); + //_analyzeTypeInfos.Add(typeInfo, ati); + if (ati.signature != null) + { + return ati.signature; + } + + var sigBuf = new StringBuilder(); + if (ati.packingSize != 0 || ati.classSize != 0 || ati.layout != LayoutKind.Sequential || !ati.blittable) + { + sigBuf.Append($"[{ati.classSize}|{ati.packingSize}|{ati.layout}|{(ati.blittable ? 0 : 1)}]"); + } + foreach (var field in ati.fields) + { + string fieldOffset = field.field.FieldOffset != null ? field.field.FieldOffset.ToString() + "|" : ""; + sigBuf.Append("{" + fieldOffset + GetOrCalculateTypeInfoSignature(ToIsomorphicType(field.type)) + "}"); + } + return ati.signature = sigBuf.ToString(); + } + + private TypeInfo ToIsomorphicType(TypeInfo type) + { + if (!type.IsStruct) + { + return type; + } + return CalculateAnalyzeTypeInfoBasic(type).isoType; + } + + private MethodDesc ToIsomorphicMethod(MethodDesc method) + { + var paramInfos = new List(); + foreach (var paramInfo in method.ParamInfos) + { + paramInfos.Add(new ParamInfo() { Type = ToIsomorphicType(paramInfo.Type) }); + } + var mbs = new MethodDesc() + { + MethodDef = method.MethodDef, + ReturnInfo = new ReturnInfo() { Type = ToIsomorphicType(method.ReturnInfo.Type) }, + ParamInfos = paramInfos, + }; + mbs.Init(); + return mbs; + } + + private List _managed2NativeMethodList; + private List _native2ManagedMethodList; + private List _adjustThunkMethodList; + + private List structTypes; + + private void BuildAnalyzeTypeInfos() + { + foreach (var type in _structTypes0) + { + ToIsomorphicType(type); + } + structTypes = _signature2Type.Values.ToList(); + structTypes.Sort((a, b) => a.TypeId - b.TypeId); + } + + private List ToUniqueOrderedList(List methods) + { + var methodMap = new SortedDictionary(); + foreach (var method in methods) + { + var sharedMethod = ToIsomorphicMethod(method); + var sig = sharedMethod.Sig; + if (!methodMap.TryGetValue(sig, out var _)) + { + methodMap.Add(sig, sharedMethod); + } + } + return methodMap.Values.ToList(); + } + + + + private static string MakeReversePInvokeSignature(MethodDesc desc, CallingConvention CallingConventionention) + { + string convStr = ((char)('A' + (int)CallingConventionention - 1)).ToString(); + return $"{convStr}{desc.Sig}"; + } + private static string MakeCalliSignature(MethodDesc desc, CallingConvention CallingConventionention) + { + string convStr = ((char)('A' + Math.Max((int)CallingConventionention - 1, 0))).ToString(); + return $"{convStr}{desc.Sig}"; + } + + private static CallingConvention GetCallingConvention(MethodDef method) + { + var monoPInvokeCallbackAttr = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute"); + if (monoPInvokeCallbackAttr == null) + { + return CallingConvention.Winapi; + } + object delegateTypeSig = monoPInvokeCallbackAttr.ConstructorArguments[0].Value; + + TypeDef delegateTypeDef; + if (delegateTypeSig is ClassSig classSig) + { + delegateTypeDef = classSig.TypeDefOrRef.ResolveTypeDefThrow(); + } + else if (delegateTypeSig is GenericInstSig genericInstSig) + { + delegateTypeDef = genericInstSig.GenericType.TypeDefOrRef.ResolveTypeDefThrow(); + } + else + { + delegateTypeDef = null; + } + + if (delegateTypeDef == null) + { + throw new NotSupportedException($"Unsupported delegate type: {delegateTypeSig}"); + } + var attr = delegateTypeDef.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute"); + if (attr == null) + { + return CallingConvention.Winapi; + } + var conv = attr.ConstructorArguments[0].Value; + return (CallingConvention)conv; + } + + private List BuildABIMethods(List rawMethods) + { + var methodsBySig = new Dictionary(); + foreach (var method in rawMethods) + { + var sharedMethod = new MethodDesc + { + MethodDef = method.Method, + ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.Method.ReturnType) }, + ParamInfos = method.Method.Parameters.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p.Type) }).ToList(), + }; + sharedMethod.Init(); + sharedMethod = ToIsomorphicMethod(sharedMethod); + + CallingConvention callingConv = GetCallingConvention(method.Method); + string signature = MakeReversePInvokeSignature(sharedMethod, callingConv); + + if (!methodsBySig.TryGetValue(signature, out var arm)) + { + arm = new ABIReversePInvokeMethodInfo() + { + Method = sharedMethod, + Signature = signature, + Count = 0, + Callvention = callingConv, + }; + methodsBySig.Add(signature, arm); + } + int preserveCount = method.GenerationAttribute != null ? (int)method.GenerationAttribute.ConstructorArguments[0].Value : 1; + arm.Count += preserveCount; + } + var newMethods = methodsBySig.Values.ToList(); + newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature)); + return newMethods; + } + + private List BuildCalliMethods(List rawMethods) + { + var methodsBySig = new Dictionary(); + foreach (var method in rawMethods) + { + var sharedMethod = new MethodDesc + { + MethodDef = null, + ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.MethodSig.RetType) }, + ParamInfos = method.MethodSig.Params.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p) }).ToList(), + }; + sharedMethod.Init(); + sharedMethod = ToIsomorphicMethod(sharedMethod); + + CallingConvention callingConv = (CallingConvention)((int)(method.MethodSig.CallingConvention & dnlib.DotNet.CallingConvention.Mask) + 1); + string signature = MakeCalliSignature(sharedMethod, callingConv); + + if (!methodsBySig.TryGetValue(signature, out var arm)) + { + arm = new CalliMethodInfo() + { + Method = sharedMethod, + Signature = signature, + Callvention = callingConv, + }; + methodsBySig.Add(signature, arm); + } + } + var newMethods = methodsBySig.Values.ToList(); + newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature)); + return newMethods; + } + + private void BuildOptimizedMethods() + { + _managed2NativeMethodList = ToUniqueOrderedList(_managed2NativeMethodList0); + _native2ManagedMethodList = ToUniqueOrderedList(_native2ManagedMethodList0); + _adjustThunkMethodList = ToUniqueOrderedList(_adjustThunkMethodList0); + _reversePInvokeMethods = BuildABIMethods(_originalReversePInvokeMethods); + _callidMethods = BuildCalliMethods(_originalCalliMethodSignatures); + } + + private void OptimizationTypesAndMethods() + { + BuildAnalyzeTypeInfos(); + BuildOptimizedMethods(); + Debug.LogFormat("== after optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}", + _managed2NativeMethodList.Count, _native2ManagedMethodList.Count, _adjustThunkMethodList.Count, structTypes.Count); + } + + private void GenerateCode() + { + var frr = new FileRegionReplace(_templateCode); + + List lines = new List(20_0000) + { + "\n", + $"// DEVELOPMENT={(_development ? 1 : 0)}", + "\n" + }; + + var classInfos = new List(); + var classTypeSet = new Dictionary(); + foreach (var type in structTypes) + { + GenerateClassInfo(type, classTypeSet, classInfos); + } + + GenerateStructDefines(classInfos, lines); + + // use structTypes0 to generate signature + GenerateStructureSignatureStub(_structTypes0, lines); + + foreach (var method in _managed2NativeMethodList) + { + GenerateManaged2NativeMethod(method, lines); + } + + GenerateManaged2NativeStub(_managed2NativeMethodList, lines); + + foreach (var method in _native2ManagedMethodList) + { + GenerateNative2ManagedMethod(method, lines); + } + + GenerateNative2ManagedStub(_native2ManagedMethodList, lines); + + foreach (var method in _adjustThunkMethodList) + { + GenerateAdjustThunkMethod(method, lines); + } + + GenerateAdjustThunkStub(_adjustThunkMethodList, lines); + + GenerateReversePInvokeWrappers(_reversePInvokeMethods, lines); + + foreach (var method in _callidMethods) + { + GenerateManaged2NativeFunctionPointerMethod(method, lines); + } + GenerateManaged2NativeFunctionPointerMethodStub(_callidMethods, lines); + + + frr.Replace("CODE", string.Join("\n", lines)); + + Directory.CreateDirectory(Path.GetDirectoryName(_outputFile)); + + frr.Commit(_outputFile); + } + + private static string GetIl2cppCallConventionName(CallingConvention conv) + { + switch (conv) + { + case 0: + case CallingConvention.Winapi: + return "DEFAULT_CALL"; + case CallingConvention.Cdecl: + return "CDECL"; + case CallingConvention.StdCall: + return "STDCALL"; + case CallingConvention.ThisCall: + return "THISCALL"; + case CallingConvention.FastCall: + return "FASTCALL"; + default: + throw new NotSupportedException($"Unsupported CallingConvention {conv}"); + } + } + + private void GenerateReversePInvokeWrappers(List methods, List lines) + { + int methodIndex = 0; + var stubCodes = new List(); + foreach (var methodInfo in methods) + { + MethodDesc method = methodInfo.Method; + string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention); + string paramDeclaringListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}")); + string paramNameListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"__arg{p.Index}").Concat(new string[] { "method" })); + string paramTypeListWithMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()}").Concat(new string[] { "const MethodInfo*" })); + string methodTypeDef = $"typedef {method.ReturnInfo.Type.GetTypeName()} (*Callback)({paramTypeListWithMethodInfoStr})"; + for (int i = 0; i < methodInfo.Count; i++, methodIndex++) + { + lines.Add($@" +{method.ReturnInfo.Type.GetTypeName()} {il2cppCallConventionName} __ReversePInvokeMethod_{methodIndex}({paramDeclaringListWithoutMethodInfoStr}) +{{ + il2cpp::vm::ScopedThreadAttacher _vmThreadHelper; + const MethodInfo* method = InterpreterModule::GetMethodInfoByReversePInvokeWrapperIndex({methodIndex}); + {methodTypeDef}; + {(method.ReturnInfo.IsVoid ? "" : "return ")}((Callback)(method->methodPointerCallByInterp))({paramNameListWithoutMethodInfoStr}); +}} + "); + stubCodes.Add($"\t{{\"{methodInfo.Signature}\", (Il2CppMethodPointer)__ReversePInvokeMethod_{methodIndex}}},"); + } + Debug.Log($"[ReversePInvokeWrap.Generator] method:{method.MethodDef} wrapperCount:{methodInfo.Count}"); + } + + lines.Add(@" +const ReversePInvokeMethodData hybridclr::interpreter::g_reversePInvokeMethodStub[] +{ +"); + lines.AddRange(stubCodes); + + lines.Add(@" + {nullptr, nullptr}, +}; +"); + } + + public void Generate() + { + PrepareMethodBridges(); + CollectTypesAndMethods(); + OptimizationTypesAndMethods(); + GenerateCode(); + } + + private void CollectStructDefs(List methods, HashSet structTypes) + { + foreach (var method in methods) + { + foreach(var paramInfo in method.ParamInfos) + { + if (paramInfo.Type.IsStruct) + { + structTypes.Add(paramInfo.Type); + if (paramInfo.Type.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{paramInfo.Type.Klass} contains generic parameter"); + } + } + + } + if (method.ReturnInfo.Type.IsStruct) + { + structTypes.Add(method.ReturnInfo.Type); + if (method.ReturnInfo.Type.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{method.ReturnInfo.Type.Klass} contains generic parameter"); + } + } + } + + } + + private void CollectStructDefs(List methods, HashSet structTypes) + { + ICorLibTypes corLibTypes = _genericMethods[0].Method.Module.CorLibTypes; + + foreach (var method in methods) + { + foreach (var paramInfo in method.Params) + { + var paramType = GetSharedTypeInfo(MetaUtil.ToShareTypeSig(corLibTypes, paramInfo)); + if (paramType.IsStruct) + { + structTypes.Add(paramType); + if (paramType.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method} type:{paramType.Klass} contains generic parameter"); + } + } + + } + var returnType = GetSharedTypeInfo(MetaUtil.ToShareTypeSig(corLibTypes, method.RetType)); + if (returnType.IsStruct) + { + structTypes.Add(returnType); + if (returnType.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method} type:{returnType.Klass} contains generic parameter"); + } + } + } + + } + + class FieldInfo + { + public FieldDef field; + public TypeInfo type; + } + + class ClassInfo + { + public TypeInfo type; + public List fields; + public uint packingSize; + public uint classSize; + public LayoutKind layout; + public bool blittable; + } + + private void GenerateClassInfo(TypeInfo type, Dictionary typeSet, List classInfos) + { + if (typeSet.ContainsKey(type)) + { + return; + } + + AnalyzeTypeInfo ati = CalculateAnalyzeTypeInfoBasic(type); + //TypeSig typeSig = type.Klass; + //var fields = new List(); + + //TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow(); + + //List klassInst = typeSig.ToGenericInstSig()?.GenericArguments?.ToList(); + //GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + //ClassLayout sa = typeDef.ClassLayout; + + //ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + //bool blittable = true; + //foreach (FieldDef field in typeDef.Fields) + //{ + // if (field.IsStatic) + // { + // continue; + // } + // TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + // fieldType = MetaUtil.ToShareTypeSig(corLibTypes, fieldType); + // var fieldTypeInfo = ToIsomorphicType(GetSharedTypeInfo(fieldType)); + // if (fieldTypeInfo.IsStruct) + // { + // GenerateClassInfo(fieldTypeInfo, typeSet, classInfos); + // } + // blittable &= IsBlittable(fieldType, fieldTypeInfo, typeSet); + // fields.Add(new FieldInfo { field = field, type = fieldTypeInfo }); + //} + + foreach (var field in ati.fields) + { + if (field.type.IsStruct) + { + GenerateClassInfo(field.type, typeSet, classInfos); + } + } + var classInfo = new ClassInfo() + { + type = type, + fields = ati.fields, + packingSize = ati.packingSize, + classSize = ati.classSize, + layout = ati.layout, + blittable = ati.blittable, + }; + typeSet.Add(type, classInfo); + classInfos.Add(classInfo); + } + + private void GenerateStructDefines(List classInfos, List lines) + { + foreach (var ci in classInfos) + { + lines.Add($"// {ci.type.Klass}"); + uint packingSize = ci.packingSize; + uint classSize = ci.classSize; + + if (ci.layout == LayoutKind.Explicit) + { + lines.Add($"struct {ci.type.GetTypeName()} {{"); + lines.Add("\tunion {"); + if (classSize > 0) + { + lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};"); + } + int index = 0; + foreach (var field in ci.fields) + { + uint offset = field.field.FieldOffset.Value; + string fieldName = $"__{index}"; + string commentFieldName = $"{field.field.Name}"; + lines.Add("\t#pragma pack(push, 1)"); + lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName};}}; // {commentFieldName}"); + lines.Add($"\t#pragma pack(pop)"); + if (packingSize > 0) + { + lines.Add($"\t#pragma pack(push, {packingSize})"); + } + lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding_forAlignmentOnly[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName}_forAlignmentOnly;}}; // {commentFieldName}"); + if (packingSize > 0) + { + lines.Add($"\t#pragma pack(pop)"); + } + ++index; + } + lines.Add("\t};"); + lines.Add("};"); + } + else + { + if (packingSize != 0) + { + lines.Add($"#pragma pack(push, {packingSize})"); + } + lines.Add($"{(classSize > 0 ? "union" : "struct")} {ci.type.GetTypeName()} {{"); + if (classSize > 0) + { + lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};"); + lines.Add("\tstruct {"); + } + int index = 0; + foreach (var field in ci.fields) + { + string fieldName = $"__{index}"; + string commentFieldName = $"{field.field.Name}"; + lines.Add($"\t{field.type.GetTypeName()} {fieldName}; // {commentFieldName}"); + ++index; + } + if (classSize > 0) + { + lines.Add("\t};"); + } + lines.Add("};"); + if (packingSize != 0) + { + lines.Add($"#pragma pack(pop)"); + } + } + } + } + + private const string SigOfObj = "u"; + + private static string ToFullName(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: return "v"; + case ElementType.Boolean: return "u1"; + case ElementType.I1: return "i1"; + case ElementType.U1: return "u1"; + case ElementType.I2: return "i2"; + case ElementType.Char: + case ElementType.U2: return "u2"; + case ElementType.I4: return "i4"; + case ElementType.U4: return "u4"; + case ElementType.I8: return "i8"; + case ElementType.U8: return "u8"; + case ElementType.R4: return "r4"; + case ElementType.R8: return "r8"; + case ElementType.I: return "i"; + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + return SigOfObj; + case ElementType.Module: + case ElementType.Var: + case ElementType.MVar: + throw new NotSupportedException($"ToFullName type:{type}"); + case ElementType.TypedByRef: return TypeInfo.strTypedByRef; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function"); + } + if (typeDef.IsEnum) + { + return ToFullName(typeDef.GetEnumUnderlyingType()); + } + return ToValueTypeFullName((ClassOrValueTypeSig)type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return SigOfObj; + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return ToFullName(typeDef.GetEnumUnderlyingType()); + } + return $"{ToValueTypeFullName(gis.GenericType)}<{string.Join(",", gis.GenericArguments.Select(a => ToFullName(a)))}>"; + } + default: throw new NotSupportedException($"{type.ElementType}"); + } + } + + private static bool IsSystemOrUnityAssembly(ModuleDef module) + { + if (module.IsCoreLibraryModule == true) + { + return true; + } + string assName = module.Assembly.Name.String; + return assName.StartsWith("System.") || assName.StartsWith("UnityEngine."); + } + + private static string ToValueTypeFullName(ClassOrValueTypeSig type) + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} resolve fail"); + } + + if (typeDef.DeclaringType != null) + { + return $"{ToValueTypeFullName((ClassOrValueTypeSig)typeDef.DeclaringType.ToTypeSig())}/{typeDef.Name}"; + } + + if (IsSystemOrUnityAssembly(typeDef.Module)) + { + return type.FullName; + } + return $"{Path.GetFileNameWithoutExtension(typeDef.Module.Name)}:{typeDef.FullName}"; + } + + private void GenerateStructureSignatureStub(List types, List lines) + { + lines.Add("const FullName2Signature hybridclr::interpreter::g_fullName2SignatureStub[] = {"); + foreach (var type in types) + { + TypeInfo isoType = ToIsomorphicType(type); + lines.Add($"\t{{\"{ToFullName(type.Klass)}\", \"{isoType.CreateSigName()}\"}},"); + } + lines.Add("\t{ nullptr, nullptr},"); + lines.Add("};"); + } + + private void GenerateManaged2NativeStub(List methods, List lines) + { + lines.Add($@" +const Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", __M2N_{method.CreateInvokeSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + private void GenerateNative2ManagedStub(List methods, List lines) + { + lines.Add($@" +const Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_{method.CreateInvokeSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + private void GenerateAdjustThunkStub(List methods, List lines) + { + lines.Add($@" +const NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_AdjustorThunk_{method.CreateCallSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + private string GetManaged2NativePassParam(TypeInfo type, string varName) + { + return $"M2NFromValueOrAddress<{type.GetTypeName()}>({varName})"; + } + + private string GetNative2ManagedPassParam(TypeInfo type, string varName) + { + return type.NeedExpandValue() ? $"(uint64_t)({varName})" : $"N2MAsUint64ValueOrAddress<{type.GetTypeName()}>({varName})"; + } + + private void GenerateManaged2NativeMethod(MethodDesc method, List lines) + { + string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" })); + string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")).Concat(new string[] { "method" })); + + lines.Add($@" +static void __M2N_{method.CreateCallSigName()}(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret) +{{ + typedef {method.ReturnInfo.Type.GetTypeName()} (*NativeMethod)({paramListStr}); + {(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(method->methodPointerCallByInterp))({paramNameListStr}); +}} +"); + } + + private string GenerateArgumentSizeAndOffset(List paramInfos) + { + StringBuilder s = new StringBuilder(); + int index = 0; + foreach (var param in paramInfos) + { + s.AppendLine($"\tconstexpr int __ARG_OFFSET_{index}__ = {(index > 0 ? $"__ARG_OFFSET_{index - 1}__ + __ARG_SIZE_{index-1}__" : "0")};"); + s.AppendLine($"\tconstexpr int __ARG_SIZE_{index}__ = (sizeof(__arg{index}) + 7)/8;"); + index++; + } + s.AppendLine($"\tconstexpr int __TOTAL_ARG_SIZE__ = {(paramInfos.Count > 0 ? $"__ARG_OFFSET_{index-1}__ + __ARG_SIZE_{index-1}__" : "1")};"); + return s.ToString(); + } + + private string GenerateCopyArgumentToInterpreterStack(List paramInfos) + { + StringBuilder s = new StringBuilder(); + int index = 0; + foreach (var param in paramInfos) + { + if (param.Type.IsPrimitiveType) + { + if (param.Type.NeedExpandValue()) + { + s.AppendLine($"\targs[__ARG_OFFSET_{index}__].u64 = __arg{index};"); + } + else + { + s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};"); + } + } + else + { + s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};"); + } + index++; + } + return s.ToString(); + } + + private void GenerateNative2ManagedMethod0(MethodDesc method, bool adjustorThunk, List lines) + { + string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" })); + lines.Add($@" +static {method.ReturnInfo.Type.GetTypeName()} __N2M_{(adjustorThunk ? "AdjustorThunk_" : "")}{method.CreateCallSigName()}({paramListStr}) +{{ + {(adjustorThunk ? "__arg0 += sizeof(Il2CppObject);" : "")} +{GenerateArgumentSizeAndOffset(method.ParamInfos)} + StackObject args[__TOTAL_ARG_SIZE__]; +{GenerateCopyArgumentToInterpreterStack(method.ParamInfos)} + {(method.ReturnInfo.IsVoid ? "Interpreter::Execute(method, args, nullptr);" : $"{method.ReturnInfo.Type.GetTypeName()} ret; Interpreter::Execute(method, args, &ret); return ret;")} +}} +"); + } + + private void GenerateNative2ManagedMethod(MethodDesc method, List lines) + { + GenerateNative2ManagedMethod0(method, false, lines); + } + + private void GenerateAdjustThunkMethod(MethodDesc method, List lines) + { + GenerateNative2ManagedMethod0(method, true, lines); + } + + private void GenerateManaged2NativeFunctionPointerMethod(CalliMethodInfo methodInfo, List lines) + { + MethodDesc method = methodInfo.Method; + string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}")); + string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]"))); + string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention); + lines.Add($@" +static void __M2NF_{methodInfo.Signature}(Il2CppMethodPointer methodPointer, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret) +{{ + typedef {method.ReturnInfo.Type.GetTypeName()} ({il2cppCallConventionName} *NativeMethod)({paramListStr}); + {(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(methodPointer))({paramNameListStr}); +}} +"); + } + + private void GenerateManaged2NativeFunctionPointerMethodStub(List calliMethodSignatures, List lines) + { + lines.Add(@" +const Managed2NativeFunctionPointerCallData hybridclr::interpreter::g_managed2NativeFunctionPointerCallStub[] +{ +"); + foreach (var method in calliMethodSignatures) + { + lines.Add($"\t{{\"{method.Signature}\", __M2NF_{method.Signature}}},"); + } + + lines.Add(@" + {nullptr, nullptr}, +}; +"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta new file mode 100644 index 00000000..914508ec --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e42a0f3bcbc5ddf438a85ae16c1b3116 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs new file mode 100644 index 00000000..917c2a14 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs @@ -0,0 +1,78 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CallingConvention = System.Runtime.InteropServices.CallingConvention; +using UnityEngine; + +namespace HybridCLR.Editor.MethodBridge +{ + public class RawMonoPInvokeCallbackMethodInfo + { + public MethodDef Method { get; set; } + + public CustomAttribute GenerationAttribute { get; set; } + } + + public class MonoPInvokeCallbackAnalyzer + { + + private readonly List _rootModules = new List(); + + private readonly List _reversePInvokeMethods = new List(); + + public List ReversePInvokeMethods => _reversePInvokeMethods; + + public MonoPInvokeCallbackAnalyzer(AssemblyCache cache, List assemblyNames) + { + foreach (var assemblyName in assemblyNames) + { + _rootModules.Add(cache.LoadModule(assemblyName)); + } + } + + private void CollectReversePInvokeMethods() + { + foreach (var mod in _rootModules) + { + Debug.Log($"ass:{mod.FullName} method count:{mod.Metadata.TablesStream.MethodTable.Rows}"); + for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++) + { + var method = mod.ResolveMethod(rid); + //Debug.Log($"method:{method}"); + if (!method.IsStatic || !method.HasCustomAttributes) + { + continue; + } + CustomAttribute wa = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute"); + if (wa == null) + { + continue; + } + if (!MetaUtil.IsSupportedPInvokeMethodSignature(method.MethodSig)) + { + Debug.LogError($"MonoPInvokeCallback method {method.FullName} has unsupported parameter or return type. Please check the method signature."); + } + //foreach (var ca in method.CustomAttributes) + //{ + // Debug.Log($"{ca.AttributeType.FullName} {ca.TypeFullName}"); + //} + _reversePInvokeMethods.Add(new RawMonoPInvokeCallbackMethodInfo() + { + Method = method, + GenerationAttribute = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "HybridCLR.ReversePInvokeWrapperGenerationAttribute"), + }); + } + } + } + + public void Run() + { + CollectReversePInvokeMethods(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs.meta new file mode 100644 index 00000000..792b5b58 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/MonoPInvokeCallbackAnalyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c172068b408c0e349b2ceee4c4635085 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs new file mode 100644 index 00000000..1ddbd3ed --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs @@ -0,0 +1,50 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.MethodBridge +{ + + public class PInvokeAnalyzer + { + private readonly List _rootModules = new List(); + + private readonly List _pinvokeMethodSignatures = new List(); + + public List PInvokeMethodSignatures => _pinvokeMethodSignatures; + + public PInvokeAnalyzer(AssemblyCache cache, List assemblyNames) + { + foreach (var assemblyName in assemblyNames) + { + _rootModules.Add(cache.LoadModule(assemblyName)); + } + } + + public void Run() + { + foreach (var mod in _rootModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.IsPinvokeImpl) + { + if (!MetaUtil.IsSupportedPInvokeMethodSignature(method.MethodSig)) + { + Debug.LogError($"PInvoke method {method.FullName} has unsupported parameter or return type. Please check the method signature."); + } + _pinvokeMethodSignatures.Add(new CallNativeMethodSignatureInfo { MethodSig = method.MethodSig }); + } + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs.meta new file mode 100644 index 00000000..3d2ae8ad --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/PInvokeAnalyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9923175c961b78849aeaf99708e294ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta new file mode 100644 index 00000000..234a1bc0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 259c1cb7fe681f74eb435ab8f268890d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta new file mode 100644 index 00000000..f73ef755 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3708ee1d4035cb14abaa4d64a8ec8148 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs new file mode 100644 index 00000000..51ecb3bf --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs @@ -0,0 +1,106 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; +using UnityEngine.UIElements; + +namespace HybridCLR.Editor.Settings +{ + public class HybridCLRSettingsProvider : SettingsProvider + { + private SerializedObject _serializedObject; + private SerializedProperty _enable; + private SerializedProperty _useGlobalIl2cpp; + private SerializedProperty _hybridclrRepoURL; + private SerializedProperty _il2cppPlusRepoURL; + private SerializedProperty _hotUpdateAssemblyDefinitions; + private SerializedProperty _hotUpdateAssemblies; + private SerializedProperty _preserveHotUpdateAssemblies; + private SerializedProperty _hotUpdateDllCompileOutputRootDir; + private SerializedProperty _externalHotUpdateAssemblyDirs; + private SerializedProperty _strippedAOTDllOutputRootDir; + private SerializedProperty _patchAOTAssemblies; + private SerializedProperty _outputLinkFile; + private SerializedProperty _outputAOTGenericReferenceFile; + private SerializedProperty _maxGenericReferenceIteration; + private SerializedProperty _maxMethodBridgeGenericIteration; + + public HybridCLRSettingsProvider() : base("Project/HybridCLR Settings", SettingsScope.Project) { } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + InitGUI(); + } + + private void InitGUI() + { + var setting = HybridCLRSettings.LoadOrCreate(); + _serializedObject?.Dispose(); + _serializedObject = new SerializedObject(setting); + _enable = _serializedObject.FindProperty("enable"); + _useGlobalIl2cpp = _serializedObject.FindProperty("useGlobalIl2cpp"); + _hybridclrRepoURL = _serializedObject.FindProperty("hybridclrRepoURL"); + _il2cppPlusRepoURL = _serializedObject.FindProperty("il2cppPlusRepoURL"); + _hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions"); + _hotUpdateAssemblies = _serializedObject.FindProperty("hotUpdateAssemblies"); + _preserveHotUpdateAssemblies = _serializedObject.FindProperty("preserveHotUpdateAssemblies"); + _hotUpdateDllCompileOutputRootDir = _serializedObject.FindProperty("hotUpdateDllCompileOutputRootDir"); + _externalHotUpdateAssemblyDirs = _serializedObject.FindProperty("externalHotUpdateAssembliyDirs"); + _strippedAOTDllOutputRootDir = _serializedObject.FindProperty("strippedAOTDllOutputRootDir"); + _patchAOTAssemblies = _serializedObject.FindProperty("patchAOTAssemblies"); + _outputLinkFile = _serializedObject.FindProperty("outputLinkFile"); + _outputAOTGenericReferenceFile = _serializedObject.FindProperty("outputAOTGenericReferenceFile"); + _maxGenericReferenceIteration = _serializedObject.FindProperty("maxGenericReferenceIteration"); + _maxMethodBridgeGenericIteration = _serializedObject.FindProperty("maxMethodBridgeGenericIteration"); + } + + public override void OnGUI(string searchContext) + { + if (_serializedObject == null || !_serializedObject.targetObject) + { + InitGUI(); + } + _serializedObject.Update(); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(_enable); + EditorGUILayout.PropertyField(_hybridclrRepoURL); + EditorGUILayout.PropertyField(_il2cppPlusRepoURL); + EditorGUILayout.PropertyField(_useGlobalIl2cpp); + EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions); + EditorGUILayout.PropertyField(_hotUpdateAssemblies); + EditorGUILayout.PropertyField(_preserveHotUpdateAssemblies); + EditorGUILayout.PropertyField(_hotUpdateDllCompileOutputRootDir); + EditorGUILayout.PropertyField(_externalHotUpdateAssemblyDirs); + EditorGUILayout.PropertyField(_strippedAOTDllOutputRootDir); + EditorGUILayout.PropertyField(_patchAOTAssemblies); + EditorGUILayout.PropertyField(_outputLinkFile); + EditorGUILayout.PropertyField(_outputAOTGenericReferenceFile); + EditorGUILayout.PropertyField(_maxGenericReferenceIteration); + EditorGUILayout.PropertyField(_maxMethodBridgeGenericIteration); + if (EditorGUI.EndChangeCheck()) + { + _serializedObject.ApplyModifiedProperties(); + HybridCLRSettings.Save(); + } + } + + public override void OnDeactivate() + { + base.OnDeactivate(); + HybridCLRSettings.Save(); + } + + static HybridCLRSettingsProvider s_provider; + + [SettingsProvider] + public static SettingsProvider CreateMyCustomSettingsProvider() + { + if (s_provider == null) + { + s_provider = new HybridCLRSettingsProvider(); + } + return s_provider; + } + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta new file mode 100644 index 00000000..0a21f377 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2bd1694fedc8b54c88bb9f6c67907d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs new file mode 100644 index 00000000..f577a95a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs @@ -0,0 +1,98 @@ +using System.IO; +using UnityEditorInternal; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + + public class HybridCLRSettings : ScriptableObject + { + [Tooltip("enable HybridCLR")] + public bool enable = true; + + [Tooltip("use il2cpp in unity editor installation location")] + public bool useGlobalIl2cpp; + + [Tooltip("hybridclr repo URL")] + public string hybridclrRepoURL = "https://gitee.com/focus-creative-games/hybridclr"; + + [Tooltip("il2cpp_plus repo URL")] + public string il2cppPlusRepoURL = "https://gitee.com/focus-creative-games/il2cpp_plus"; + + [Tooltip("hot update assembly definitions(asd)")] + public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions; + + [Tooltip("hot update assembly names(without .dll suffix)")] + public string[] hotUpdateAssemblies; + + [Tooltip("preserved hot update assembly names(without .dll suffix)")] + public string[] preserveHotUpdateAssemblies; + + [Tooltip("output directory of compiling hot update assemblies")] + public string hotUpdateDllCompileOutputRootDir = "HybridCLRData/HotUpdateDlls"; + + [Tooltip("searching paths of external hot update assemblies")] + public string[] externalHotUpdateAssembliyDirs; + + [Tooltip("output directory of stripped AOT assemblies")] + public string strippedAOTDllOutputRootDir = "HybridCLRData/AssembliesPostIl2CppStrip"; + + [Tooltip("supplementary metadata assembly names(without .dll suffix)")] + public string[] patchAOTAssemblies; + + [Tooltip("output file of automatic generated link.xml by scanning hot update assemblies")] + public string outputLinkFile = "HybridCLRGenerate/link.xml"; + + [Tooltip("output file of automatic generated AOTGenericReferences.cs")] + public string outputAOTGenericReferenceFile = "HybridCLRGenerate/AOTGenericReferences.cs"; + + [Tooltip("max iteration count of searching generic methods in hot update assemblies")] + public int maxGenericReferenceIteration = 10; + + [Tooltip("max iteration count of searching method bridge generic methods in AOT assemblies")] + public int maxMethodBridgeGenericIteration = 10; + + + + private static HybridCLRSettings s_Instance; + + public static HybridCLRSettings Instance + { + get + { + if (!s_Instance) + { + LoadOrCreate(); + } + return s_Instance; + } + } + + private static string GetFilePath() + { + return "ProjectSettings/HybridCLRSettings.asset"; + } + + public static HybridCLRSettings LoadOrCreate() + { + string filePath = GetFilePath(); + Object[] objs = InternalEditorUtility.LoadSerializedFileAndForget(filePath); + s_Instance = objs.Length > 0 ? (HybridCLRSettings)objs[0] : (s_Instance ?? CreateInstance()); + return s_Instance; + } + + public static void Save() + { + if (!s_Instance) + { + return; + } + + string filePath = GetFilePath(); + string directoryName = Path.GetDirectoryName(filePath); + Directory.CreateDirectory(directoryName); + var obj = new Object[1] { s_Instance }; + InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, true); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta new file mode 100644 index 00000000..570bc8fa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e189374413a3f00468e49d51d8b27a09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs new file mode 100644 index 00000000..c21c46da --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs @@ -0,0 +1,39 @@ +using HybridCLR.Editor.Installer; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + public static class MenuProvider + { + + [MenuItem("HybridCLR/About", priority = 0)] + public static void OpenAbout() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/intro"); + + [MenuItem("HybridCLR/Installer...", priority = 60)] + private static void Open() + { + InstallerWindow window = EditorWindow.GetWindow("HybridCLR Installer", true); + window.minSize = new Vector2(800f, 500f); + } + + [MenuItem("HybridCLR/Settings...", priority = 61)] + public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + + [MenuItem("HybridCLR/Documents/Quick Start")] + public static void OpenQuickStart() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart"); + + [MenuItem("HybridCLR/Documents/Performance")] + public static void OpenPerformance() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/basic/performance"); + + [MenuItem("HybridCLR/Documents/FAQ")] + public static void OpenFAQ() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/faq"); + + [MenuItem("HybridCLR/Documents/Common Errors")] + public static void OpenCommonErrors() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors"); + + [MenuItem("HybridCLR/Documents/Bug Report")] + public static void OpenBugReport() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/issue"); + } + +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta new file mode 100644 index 00000000..a4474b27 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a475a5f281298b84da32373694704c68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs new file mode 100644 index 00000000..0aaa2697 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using HybridCLR.Editor.Settings; + + +namespace HybridCLR.Editor +{ + public static class SettingsUtil + { + public static bool Enable + { + get => HybridCLRSettings.Instance.enable; + set + { + HybridCLRSettings.Instance.enable = value; + HybridCLRSettings.Save(); + } + } + + public static string PackageName { get; } = "com.code-philosophy.hybridclr"; + + public static string HybridCLRDataPathInPackage => $"Packages/{PackageName}/Data~"; + + public static string TemplatePathInPackage => $"{HybridCLRDataPathInPackage}/Templates"; + + public static string ProjectDir { get; } = Directory.GetParent(Application.dataPath).ToString(); + + public static string ScriptingAssembliesJsonFile { get; } = "ScriptingAssemblies.json"; + + public static string HotUpdateDllsRootOutputDir => HybridCLRSettings.Instance.hotUpdateDllCompileOutputRootDir; + + public static string AssembliesPostIl2CppStripDir => HybridCLRSettings.Instance.strippedAOTDllOutputRootDir; + + public static string HybridCLRDataDir => $"{ProjectDir}/HybridCLRData"; + + public static string LocalUnityDataDir => $"{HybridCLRDataDir}/LocalIl2CppData-{Application.platform}"; + + public static string LocalIl2CppDir => $"{LocalUnityDataDir}/il2cpp"; + + public static string GeneratedCppDir => $"{LocalIl2CppDir}/libil2cpp/hybridclr/generated"; + + public static string Il2CppBuildCacheDir { get; } = $"{ProjectDir}/Library/Il2cppBuildCache"; + + public static string GlobalgamemanagersBinFile { get; } = "globalgamemanagers"; + + public static string Dataunity3dBinFile { get; } = "data.unity3d"; + + public static string GetHotUpdateDllsOutputDirByTarget(BuildTarget target) + { + return $"{HotUpdateDllsRootOutputDir}/{target}"; + } + + public static string GetAssembliesPostIl2CppStripDir(BuildTarget target) + { + return $"{AssembliesPostIl2CppStripDir}/{target}"; + } + + class AssemblyDefinitionData + { + public string name; + } + + public static List HotUpdateAssemblyNamesExcludePreserved + { + get + { + var gs = HybridCLRSettings.Instance; + var hotfixAssNames = (gs.hotUpdateAssemblyDefinitions ?? Array.Empty()).Select(ad => JsonUtility.FromJson(ad.text)); + + var hotfixAssembles = new List(); + foreach (var assName in hotfixAssNames) + { + hotfixAssembles.Add(assName.name); + } + hotfixAssembles.AddRange(gs.hotUpdateAssemblies ?? Array.Empty()); + return hotfixAssembles.ToList(); + } + } + + public static List HotUpdateAssemblyFilesExcludePreserved => HotUpdateAssemblyNamesExcludePreserved.Select(dll => dll + ".dll").ToList(); + + + public static List HotUpdateAssemblyNamesIncludePreserved + { + get + { + List allAsses = HotUpdateAssemblyNamesExcludePreserved; + string[] preserveAssemblyNames = HybridCLRSettings.Instance.preserveHotUpdateAssemblies; + if (preserveAssemblyNames != null && preserveAssemblyNames.Length > 0) + { + foreach (var assemblyName in preserveAssemblyNames) + { + if (allAsses.Contains(assemblyName)) + { + throw new Exception($"[HotUpdateAssemblyNamesIncludePreserved] assembly:'{assemblyName}' is duplicated"); + } + allAsses.Add(assemblyName); + } + } + + return allAsses; + } + } + + public static List HotUpdateAssemblyFilesIncludePreserved => HotUpdateAssemblyNamesIncludePreserved.Select(ass => ass + ".dll").ToList(); + + public static List AOTAssemblyNames => HybridCLRSettings.Instance.patchAOTAssemblies.ToList(); + + public static HybridCLRSettings HybridCLRSettings => HybridCLRSettings.Instance; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta new file mode 100644 index 00000000..323dcf6c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 381c08faeafbc004f97504eeba87380d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template.meta new file mode 100644 index 00000000..f2430d54 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9af6345cc5ab1ae4a81262ab4b537911 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs new file mode 100644 index 00000000..797477bb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Template +{ + public class FileRegionReplace + { + private readonly string _tplCode; + + private readonly Dictionary _regionReplaceContents = new Dictionary(); + + public FileRegionReplace(string tplCode) + { + _tplCode = tplCode; + } + + public void Replace(string regionName, string regionContent) + { + _regionReplaceContents.Add(regionName, regionContent); + } + + public string GenFinalString() + { + string originContent = _tplCode; + + string resultContent = originContent; + + foreach (var c in _regionReplaceContents) + { + resultContent = ReplaceRegion(resultContent, c.Key, c.Value); + } + return resultContent; + } + + public void Commit(string outputFile) + { + string dir = Path.GetDirectoryName(outputFile); + Directory.CreateDirectory(dir); + string resultContent = GenFinalString(); + var utf8WithoutBOM = new System.Text.UTF8Encoding(false); + File.WriteAllText(outputFile, resultContent, utf8WithoutBOM); + } + + public static string ReplaceRegion(string resultText, string region, string replaceContent) + { + int startIndex = resultText.IndexOf("//!!!{{" + region); + if (startIndex == -1) + { + throw new Exception($"region:{region} start not find"); + } + int endIndex = resultText.IndexOf("//!!!}}" + region); + if (endIndex == -1) + { + throw new Exception($"region:{region} end not find"); + } + int replaceStart = resultText.IndexOf('\n', startIndex); + int replaceEnd = resultText.LastIndexOf('\n', endIndex); + if (replaceStart == -1 || replaceEnd == -1) + { + throw new Exception($"region:{region} not find"); + } + if (resultText.Substring(replaceStart, replaceEnd - replaceStart) == replaceContent) + { + return resultText; + } + resultText = resultText.Substring(0, replaceStart) + "\n" + replaceContent + "\n" + resultText.Substring(replaceEnd); + return resultText; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta new file mode 100644 index 00000000..14a642a5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15d4563ad83546c42bc65c99be9bd54a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE b/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE new file mode 100644 index 00000000..6c62df12 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Code Philosophy Technology Ltd. + +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. diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE.meta new file mode 100644 index 00000000..d5478d58 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f09e582706a5776448316f6c584e63a6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Plugins.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Plugins.meta new file mode 100644 index 00000000..ccdd1270 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a26873724919287449e2c9eec68ef1da +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Plugins/LZ4.dll b/UnityProject/Packages/com.code-philosophy.hybridclr/Plugins/LZ4.dll new file mode 100644 index 0000000000000000000000000000000000000000..f1aeba3415ce2e1898e27f5cd46f2a3c99f9e48f GIT binary patch literal 38912 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 +
+ +[README 中文](./README.md) | [README English](./README_EN.md) + +[Github](https://github.com/focus-creative-games/hybridclr) | [Gitee](https://gitee.com/focus-creative-games/hybridclr) + +HybridCLR是一个**特性完整、零成本、高性能、低内存**的**近乎完美**的Unity全平台原生c#热更新解决方案。 + +HybridCLR扩充了il2cpp运行时代码,使它由纯[AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime变成AOT+Interpreter 混合runtime,进而原生支持动态加载assembly,从底层彻底支持了热更新。使用HybridCLR技术的游戏不仅能在Android平台,也能在IOS、Consoles、WebGL等所有il2cpp支持的平台上高效运行。 + +由于HybridCLR对ECMA-335规范 的良好支持以及对Unity开发工作流的高度兼容,Unity项目在接入HybridCLR后,可以几乎无缝地获得C#代码热更新的能力,开发者不需要改变日常开发习惯和要求。HybridCLR首次实现了将Unity平台的全平台代码热更新方案的工程难度降到几乎为零的水平。 + +欢迎拥抱现代原生C#热更新技术 !!! + +## 文档 + +- [官方文档](https://hybridclr.doc.code-philosophy.com/docs/intro) +- [快速上手](https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart) +- [商业项目案例](https://hybridclr.doc.code-philosophy.com/docs/other/businesscase) + +## 特性 + +- 近乎完整实现了[ECMA-335规范](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/),只有极少量的[不支持的特性](https://hybridclr.doc.code-philosophy.com/docs/basic/notsupportedfeatures)。 +- 零学习和使用成本。对绝大多数开发者来说写代码近乎没有限制。 热更新代码与AOT代码无缝工作,可以随意写继承、**泛型**、**反射**之类的代码。不需要额外写任何特殊代码、没有代码生成 +- 完全支持多线程,包含但不限于 volatile、ThreadStatic、async Task等相关功能和特性。这是其他所有热更新方案都不支持的 +- 几乎完全兼容Unity的工作流。包括且不限于支持热更新**MonoBehaviour**、ScriptableObject、**DOTS**技术,资源上挂载的热更新脚本可以正确实例化,这是其他所有热更新方案都不支持的 +- 执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案。[性能测试报告](https://hybridclr.doc.code-philosophy.com/docs/basic/performance) +- 内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。[内存占用报告](https://hybridclr.doc.code-philosophy.com/docs/basic/memory) +- 支持MonoPInvokeCallback,可以与native代码或者其他语言如lua、javascript、python良好交互 +- 支持PInvoke +- 支持一些il2cpp不支持的特性,如\_\_makeref、 \_\_reftype、\_\_refvalue指令 +- 支持独创的 **Differential Hybrid Execution(DHE)** 差分混合执行技术,即可以对AOT dll任意增删改,会智能地让未改动的函数以AOT方式运行,变化或者新增的函数以interpreter模式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平 +- 支持**热重载**技术,可以100%卸载程序集 +- 支持动态**Hotfix**技术,可以运行过程中无感修复代码bug +- 支持现代的dll加密技术,有效保障代码安全 + +## 支持的版本与平台 + +- 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x、2023.2.x、6000.x.y 所有LTS版本 +- 支持所有il2cpp支持的平台 +- 支持团结引擎和鸿蒙平台 + +## 工作原理 + +HybridCLR从mono的 [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) 技术中得到启发,为unity的il2cpp之类的AOT runtime额外提供了interpreter模块,将它们由纯AOT运行时改造为"AOT + Interpreter"混合运行方式。 + +![icon](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/architecture.png) + +更具体地说,HybridCLR做了以下几点工作: + +- 实现了一个高效的元数据(dll)解析库 +- 改造了元数据管理模块,实现了元数据的动态注册 +- 实现了一个IL指令集到自定义的寄存器指令集的compiler +- 实现了一个高效的寄存器解释器 +- 额外提供大量的instinct函数,提升解释器性能 + +## 稳定性状况 + +HybridCLR已经被广泛验证是非常高效、稳定的Unity热更新解决方案,良好满足大中型商业项目的稳定和性能要求。 + +目前已经有数千个商业游戏项目接入了HybridCLR,其中有超过千个已经在App Store和Google Player上线,仅仅iOS免费榜前500名中就有近百款使用了HybridCLR。上线的项目中包括MMORPG、重度卡牌、重度塔防之类的游戏。国内绝大多数**Top游戏公司**都已经在使用HybridCLR。 + +可查看我们已知的头部公司中使用HybridCLR并且已经上线的[项目列表](https://hybridclr.doc.code-philosophy.com/docs/other/businesscase)。 + +## 支持与联系 + +- 官方1群(3000人):651188171(满) +- 新手1群(3000人):428404198(满) +- 新手2群(2000人):680274677(满) +- 新手3群(2000人):**920714552(推荐)** +- discord频道 `https://discord.gg/BATfNfJnm2` +- 商业合作邮箱: business#code-philosophy.com +- [商业化支持](https://hybridclr.doc.code-philosophy.com/docs/business/intro) + +## 关于作者 + +**walon** :**Code Philosophy(代码哲学)** 创始人 + +毕业于清华大学物理系,2006年CMO金牌,奥数国家集训队成员,保送清华基科班。专注于游戏技术,擅长开发架构和基础技术设施。 + +## license + +HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/README.md.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/README.md.meta new file mode 100644 index 00000000..b674416e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b4b6051e2483d664facc72a5102dcffc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md b/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md new file mode 100644 index 00000000..b57bd006 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md @@ -0,0 +1,88 @@ + +- [README Chinese](./README.md) +- [README English](./README_EN.md) + +# HybridCLR + +[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) + +![logo](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/logo.jpg) + +
+
+ +HybridCLR is a **feature-complete, zero-cost, high-performance, low-memory** **near-perfect** Unity cross-platform native C# hot update solution. + +HybridCLR extends the il2cpp runtime code, transforming it from a pure [AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime to an AOT+Interpreter hybrid runtime, thereby natively supporting the dynamic loading of assemblies and fundamentally supporting hot updates from the bottom layer. Games using HybridCLR technology can not only run efficiently on the Android platform but also on all platforms supported by il2cpp, including iOS, Consoles, WebGL, etc. + +Thanks to HybridCLR's good support for the ECMA-335 specification and its high compatibility with the Unity development workflow, Unity projects can almost seamlessly gain the ability to hot update C# code after integrating HybridCLR. Developers do not need to change their daily development habits and requirements. HybridCLR is the first to achieve the engineering difficulty of a full-platform code hot update solution for the Unity platform to almost zero. + +Welcome to embrace modern native C# hot update technology! + +## Documentation + +- [Official Documentation](https://hybridclr.doc.code-philosophy.com/en/docs/intro) +- [Quick Start](https://hybridclr.doc.code-philosophy.com/en/docs/beginner/quickstart) +- [Business Project Cases](https://hybridclr.doc.code-philosophy.com/en/docs/other/businesscase) + +## Features + +- Nearly complete implementation of the [ECMA-335 specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/), with only a very small number of [unsupported features](https://hybridclr.doc.code-philosophy.com/en/docs/basic/notsupportedfeatures). +- Zero learning and usage costs. For most developers, writing code is almost unrestricted. Hot update code works seamlessly with AOT code, allowing for inheritance, **generics**, **reflection**, and other code without additional special code or code generation. +- Full support for multithreading, including but not limited to volatile, ThreadStatic, async Task, and related features and characteristics. This is not supported by any other hot update solution. +- Almost complete compatibility with Unity's workflow. This includes support for hot updating **MonoBehaviour**, ScriptableObject, **DOTS** technology, and correctly instantiating hot update scripts mounted on resources, which is not supported by any other hot update solution. +- Efficient execution. A highly efficient register interpreter has been implemented, with all indicators significantly better than other hot update solutions. [Performance Test Report](https://hybridclr.doc.code-philosophy.com/en/docs/basic/performance) +- Efficient memory usage. Classes defined in hot update scripts occupy the same memory space as ordinary C# classes, far superior to other hot update solutions. [Memory Usage Report](https://hybridclr.doc.code-philosophy.com/en/docs/basic/memory) +- Supports MonoPInvokeCallback, enabling good interaction with native code or other languages such as Lua, JavaScript, Python. +- Supports some features not supported by il2cpp, such as __makeref, __reftype, __refvalue instructions. +- Supports the unique **Differential Hybrid Execution (DHE)** technology, which allows for arbitrary additions, deletions, and modifications to AOT DLLs. It intelligently runs unchanged functions in AOT mode and changed or newly added functions in interpreter mode, bringing the performance of hot-updated game logic close to that of native AOT. +- Supports **hot reload** technology, allowing 100% unloading of assemblies. +- Supports modern DLL encryption technology to effectively protect code security. + +## Supported Versions and Platforms + +- Supports all LTS versions including 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x, 2023.2.x, 6000.x.y. +- Supports all platforms supported by il2cpp. +- Supports Tuanjie(China) Engine and HarmonyOS platform. + +## Working Principle + +HybridCLR draws inspiration from Mono's [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) technology, providing an interpreter module for AOT runtimes like Unity's il2cpp, transforming them from pure AOT runtimes to "AOT + Interpreter" hybrid operation modes. + +![icon](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/architecture.png) + +More specifically, HybridCLR has done the following: + +- Implemented an efficient metadata (dll) parsing library. +- Modified the metadata management module to achieve dynamic registration of metadata. +- Implemented a compiler that converts IL instructions to a custom register instruction set. +- Implemented an efficient register interpreter. +- Provided a large number of instinct functions to enhance interpreter performance. + +## Stability Status + +HybridCLR has been widely verified as an efficient and stable Unity hot update solution, meeting the stability and performance requirements of medium and large commercial projects. + +Currently, thousands of commercial game projects have integrated HybridCLR, with over a thousand already launched on the App Store and Google Play. Nearly a hundred of the top 500 free iOS games use HybridCLR, including MMORPGs, heavy card games, and heavy tower defense games. Most of the **Top Game Companies** in China are already using HybridCLR. + +You can view the [list of known top companies using HybridCLR and their launched projects](https://hybridclr.doc.code-philosophy.com/en/docs/other/businesscase). + +## Support and Contact + +- Official Group 1: 651188171 (Full) +- Beginner Group 1: 428404198 (Full) +- Beginner Group 2: 680274677 (Full) +- Beginner Group 3: **920714552 (Recommended)** +- Discord channel https://discord.gg/BATfNfJnm2 +- Business cooperation email: business#code-philosophy.com +- [Commercial Support](https://hybridclr.doc.code-philosophy.com/en/docs/business/intro) + +## About the Author + +**walon**: Founder of **Code Philosophy (代码哲学)** + +Graduated from the Department of Physics at Tsinghua University, gold medalist of the 2006 CMO, member of the National Mathematical Olympiad Training Team, and sent to the Basic Science Class of Tsinghua. Focused on game technology, proficient in development architecture and basic technical infrastructure. + +## License + +HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license. diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md.meta new file mode 100644 index 00000000..2da64ec2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/README_EN.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c9e34f237251ef44193538977db6b15f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md b/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md new file mode 100644 index 00000000..372cb43f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md @@ -0,0 +1,977 @@ +# ReleaseLog + +## 8.3.0 + +Release Date: 2025-07-04. + +### Runtime + +- [fix] fix the bug where RuntimeInitClassCCtor was executed during InterpreterModule::GetInterpMethodInfo. This caused the type static constructor to be incorrectly executed prematurely during PrejitMethod. +- [fix] fix bug that JitMethod jit method of generic class incorrectly. +- [merge] merge il2cpp of tuanjie changes from 1.5.0-1.6.0 + +### Editor + +- [fix] fix the bug that not collect struct in calli and extern method signature in generating MethodBridge. + +## 8.2.0 + +Release Date: 2025-06-12. + +### Runtime + +- [fix] fix line number mistake in stacktrace +- [fix] Fixed bug that PDBImage::SetupStackFrameInfo didn't set ilOffset and sourceCodeLineNumber of stackFrame when SequencePoint not found +- [merge] merge il2cpp changes from 2022.3.54-2022.3.63 + +### Editor + +- [change] changed from throw exception to logError when not supported pinvoke or reverse pinvoke method parameter type was found + +## 8.1.0 + +Release Date: 2025-05-29. + +### Runtime + +- [opt] **important**! use std::unordered_set for s_GenericInst to reduce the time cost of Assembly.Load to 33% of the original. + +### Editor + +- [fix] fix bug of GenericArgumentContext that inflate ByRef and SZArray to Ptr. + +## 8.0.0 + +Release Date: 2025-05-02. + +### Runtime + +- [new] support define PInvoke method in interpreter assembly +- [new] InterpreterImage initialize ImplMap for PInvoke methods. +- [new] RawImageBase support ModuleRef and ImplMap table. +- [fix] fixed a compilation error on PS4 platform for the code `TokenGenericContextType key = { token, genericContext };` — the C++ compiler version on PS4 is too old to support this initialization syntax for std::tuple. + +### Editor + +- [fix] fix error of computing CallingConvention in MethodBridge/Generator::BuildCalliMethods +- [new] generate Managed2NativeFunction for PInvoke method +- [change] AssemblyResolver also resolves `*.dll.bytes` files besides `*.dll`. +- [change] change type of the first argument `methodPointer` of Managed2NativeFunctionPointer from `const void*` to `Il2CppMethodPointer` +- [change] the shared type of ElementType.FnPtr is changed from IntPtr to UIntPtr +- [change] validate unsupported parameter type(.e.g string) in MonoPInvokeCallback signature when generate MethodBridge file +- [opt] optimization unnecessary initialization of typeArgsStack and methodArgsStack of GenericArgumentContext +- [refactor] refactor code of settings. +- [refactor] move ReversePInvokeWrap/Analyzer.cs to MethodBridge/MonoPInvokeCallbackAnalyzer.cs + +## 7.10.0 + +Release Date: 2025-04-22. + +### Runtime + +- [fix] fix the bug that doesn't lock g_MetadataLock in PDBImage::SetupStackFrameInfo. +- [change] remove Il2CppTypeHash and Il2CppTypeEqualTo, replace with il2cpp::metadata::Il2CppTypeHash and il2cpp::metadata::Il2CppTypeEqualityComparer. +- [merge] merge il2cpp changes from tuanjie 1.3.4 to 1.5.0, base unity from 2022.3.48 to 2022.3.55 . + +### Editor + +- [fix] fix bug of `CompileDll(BuildTarget target)` that use EditorUserBuildSettings.activeBuildTarget instead of target to call CompileDll. +- [opt] AOTAssemblyMetadataStripper strips AOT assembly resources. (#54) + +## 7.9.0 + +Release Date: 2025-03-31. + +### Runtime + +- [merge] merge il2cpp changes from 6000.0.30f1 to 6000.0.44f1 + +## 7.8.1 + +Release Date: 2025-03-24. + +### Runtime + +- [fix] fix bug of CreateInitLocals when size <= 16 +- [change] remove unnecessary `frame->ip = (byte*)ip;` assignment in LOAD_PREV_FRAME() + +## 7.8.0 + +Release Date: 2025-03-24. + +### Runtime + +- [opt] fixed a **critical** bug where taking the address of the ip variable severely impacted compiler optimizations, leading to significant performance degradation. +- [opt] add HiOpCodeEnum::None case to interpreter loop. avoid decrement *ip when compute jump table, boosts about 5% performance. +- [opt] opt InitLocals and InitInlineLocals in small size cases +- [opt] reorder MethodInfo fields to reduce memory size + +### Editor + +- [fix] fixed the bug where BashUtil.RemoveDir failed to run under certain circumstances on macOS systems. + +## 7.7.0 + +Release Date: 2025-03-12. + +### Runtime + +- [change] fixed the issue that HYBRIDCLR_ENABLE_PROFILER was disabled in release build +- [fix] fix a crash in PDBImage::SetMethodDebugInfo when GetMethodDataFromCache returns nullptr +- [fix] fix assert bug of InterpreterDelegateInvoke when method->parameters_count - curMethod->parameters_count == 1 +- [fix] fix compiler error of initialize constructor code `{a, b}` for `std::tuple` in PS5 +- [opt] removed unnecessary pdb lock in PDBImage +- [change] fix some compiler warnings +- [change] HYBRIDCLR_ENABLE_STRACKTRACE was enabled in both DEBUG and RELEASE build without considering HYBRIDCLR_ENABLE_STRACE_TRACE_IN_WEBGL_RELEASE_BUILD flag. + +### Editor + +- [fix] fixed hook failed in version below MacOS 11 +- [change] CompileDllActiveBuildTarget and GenerateAll use EditorUserBuildSettings.development to compile hot update dll. +- [remove] remove option HybridCLRSettings.enableProfilerInReleaseBuild +- [remove] remove option HybridCLRSettings.enableStraceTraceInWebGLReleaseBuild + +## 7.6.0 + +Release Date: 2025-03-01. + +### Runtime + +- [fix] fixed the bug in ClassFieldLayoutCalculator where it incorrectly handles [StructLayout] and blittable attribute when calculating the layout for structs. +- [fix] fix bug of computing explicit struct layout caused by commit "199b1b1a789d760828bd33e7e1438261cd1f8d15" +- [fix] fix the code `TokenGenericContextType key = { token, genericContext }` has compiler error in PS5 +- [merge] merge il2cpp changes from 2021.3.44f1 to 2021.3.49f1 + +### Editor + +- [fix] fixed the bug in the MethodBridge generator where it incorrectly handles [StructLayout] and blittable attribute when generating code for struct classes. +- [new] add AssemblySorter to sort assemblies by reference order + +## 7.5.0 + +Release Date: 2025-02-05. + +### Editor + +- [revert] Revert 'support preserve UnityEngine core types when GenerateLinkXml'. + +## 7.4.1 + +Release Date: 2025-01-19. + +### Editor + +[fix] fixe the bug that preserving UnityEngine.PerformanceReportingModule when generating link.xml would cause the Android app built with Unity 2019 to crash on startup. + +## 7.4.0 + +Release Date: 2025-01-17. + +### Runtime + +- [new] calli supports call both native function pointer and managed method + +### Editor + +- [new] add Managed2NativeFunctionPointer MethodBridge functions +- [new] support preserve UnityEngine core types when GenerateLinkXml +- [fix] fixed the bug in AOTAssemblyMetadataStripper::Strip where ModuleWriterOptions MetadataFlags.PreserveRids was not used. +- [fix] fixed the bug where StripAOTDllCommand did not set BuildPlayerOptions.subtarget in Unity 2021+ versions, causing failure when publishing dedicated buildTarget. +- [change] add UnityVersion.h.tpl and AssemblyManifest.cpp.tpl, Il2CppDefGenerator doesn't generates and override code file from same one +- [change] add MethodBridge.cpp.tpl. MethodBridgeGeneratorCommand doesn't generate and override from same file + +## 7.3.0 + +Release Date: 2024-12-31. + +### Runtime + +- [fix] fix bug that Image::ReadRuntimeHandleFromMemberRef didn't inflate parent type when read field of GenericType +- [fix] fix an issue occurred in InterpreterImage::GenerateCustomAttributesCacheInternal where HYBRIDCLR_METADATA_MALLOC was incorrectly used to allocate the cache. When occasional contention occurs, releasing memory using HYBRIDCLR_FREE causes a crash. +- [fix] fixed a potential deadlock issue in Unity 2019 and 2020 versions within InterpreterImage::GenerateCustomAttributesCacheInternal, where il2cpp::vm::g_MetadataLock was held before running ConstructCustomAttribute. +- [fix] fixed a bug in Unity 2019 and 2020 within InterpreterImage::GenerateCustomAttributesCacheInternal, where cache memory leaks occurred under multithreading contention. +- [fix] fix the bug that InterpreterImage::ConstructCustomAttribute doesn't set write barrier for field +- [fix] fix the bug that `InterpreterImage::InitTypeDefs_2` runs after `InitClassLayouts`, causing the `packingSize` field to be incorrectly initialized. +- [fix] fix the bug in ClassFieldLayoutCalculator::LayoutFields where the alignment calculation incorrectly considers naturalAlignment, resulting in field offsets that are inconsistent with the actual field offsets in AOT. This bug originates from IL2CPP itself and only occurs in Unity 2021 and earlier versions. + +### Editor + +- [fix] fix the issue in Unity 6000 where the modification of the trimmed AOT DLL output directory for the visionOS build target caused CopyStrippedAOTAssemblies::GetStripAssembliesDir2021 to fail in copying AOT DLLs. +- [fix] fix the bug where MissingMetadataChecker can't detect references to newly added AOT assemblies. + +## 7.2.0 + +Release Date: 2024-12-9. + +### Runtime + +- [fix] fix a critical bug in Image::ReadArrayType, where it incorrectly uses alloca to allocate Il2CppArray's sizes and lobounds data. +- [merge] merge il2cpp changes from 2022.3.51f1 to 2022.3.54f1 +- [merge] merge il2cpp changes from 6000.0.21 to 6000.0.30 + +## 7.1.0 + +Release Date: 2024-12-4. + +### Runtime + +- [new] support prejit interpreter class and method +- [new] add RuntimeOptionId::MaxInlineableMethodBodySize +- [merge] merge il2cpp changes from tuanjie v1.3.1 to v1.3.4 +- [fix] fix memory leak of TransformContext::irbbs and ir2offsetMap +- [opt] does not insert CheckThrowIfNull check when inlining constructors +- [opt] remove unnecessary typeSize calculation from the NewValueTypeInterpVar instruction. +- [change] change default maxInlineableMethodBodySize from 16 to 32 +- [change] remove the unnecessary Inflate operation on arg.type when initializing ArgVarInfo in TransformContext::TransformBodyImpl. +- [change] remove unnecessary fields genericContext, klassContainer, methodContainer from the TransformContext + +### Editor + +- [new] support prejit interpreter class and method +- [new] add RuntimeOptionId::MaxInlineableMethodBodySize +- [fix] fix the bug that CopyStrippedAOTAssemblies didn't work on UWP platform of 6000.0.x +- [fix] fix the issue that CopyStrippedAOTAssemblies didn't support HMIAndroid in tuanjie engine +- [change] change the attributes on fields of HybridCLRSettings from `[Header]` to `[ToolTip]` +- [refactor] refactor code comments and translate them to English + +## 7.0.0 + +Release Date: 2024-11-15. + +### Runtime + +- [new] support method inlining +- [refactor] refactor Transform codes + +### Editor + +- [new] add option RuntimeOptionId::MaxMethodBodyCacheSize and RuntimeOptionId::MaxMethodInlineDepth +- [fix] fix the bug in GenericReferenceWriter where _systemTypePattern did not properly escape the '.' in type names. This caused issues when compiler-generated anonymous types and functions contained string sequences like 'System-Int', incorrectly matching them to 'System.Int', resulting in runtime exceptions. +- [fix] fix the bug in `MissingMetadataChecker` where it did not check for missing fields. + +## 6.11.0 + +Release Date: 2024-10-31. + +### Runtime + +- [merge] Merges changes from Tuanjie versions 1.3.0 to 1.3.1 +- [merge] Merges il2cpp code changes from version 2022.3.48f1 to 2022.3.51f1 + +## 6.10.1 + +Release Date: 2024-10-24. + +### Editor + +- [fix] Fixs HookUtils compile errors in Unity 2019 and 2020 +- [change] remove README_zh.md.meta, add README_EN.md.meta + +## 6.10.0 + +Release Date: 2024-10-23. + +### Runtime + +- [new] Officially supports 6000.0.23f LTS version +- [merge] Merges changes from Tuanjie versions 1.2.6 to 1.3.0 +- [fix] Fixed an issue in MonoHook where processorType was not handled correctly on some CPUs when the processorType was returned in all uppercase (e.g., some machines return 'INTEL' instead of 'Intel'). + +## 6.9.0 + +Release Date: 2024-9-30. + +### Runtime + +- [fix] Fixes the bug where thrown exceptions did not call `il2cpp::vm::Exception::PrepareExceptionForThrow`, resulting in an empty stack trace. +- [merge] Merges changes from versions 2021.3.42f1 to 2021.3.44f1, fixing compilation errors introduced by il2cpp changes in version 2021.3.44. +- [merge] Merges changes from versions 2022.3.41f1 to 2022.3.48f1, fixing compilation errors introduced by il2cpp changes in version 2022.3.48. +- [merge] Merges code from 6000.0.19f1 to 6000.0.21f1, fixing compilation errors introduced by il2cpp changes in version 6000.0.20. +- [merge] Merges changes from Tuanjie versions 1.1.0 to 1.2.6 + +## 6.8.0 + +Release Date: 2024-9-14. + +### Runtime + +- [fix] Fixes the bug where exception stacks did not include line numbers. +- [fix] Fixes the bug where `Il2CppGenericContextCompare` simply compared class_inst and method_inst pointers for equality, which is not the case for all GenericInst (e.g., `s_Il2CppMetadataRegistration->genericInsts`) that do not come from GenericInstPool, hence identical GenericInst are not pointer equal. +- [merge] Merges il2cpp code changes from version 6000.0.10 to 6000.0.19 + +## 6.7.1 + +Release Date: 2024-8-26. + +### Runtime + +- [fix] Fixes the bug where there were compilation errors when publishing to the iOS platform with Unity 2019. + +## 6.7.0 + +Release Date: 2024-8-26. + +### Runtime + +- [opt] No longer enables PROFILER in Release compilation mode, this optimization reduces the overhead of function calls by 10-15%, and overall performance is improved by approximately 2-4%. +- [opt] When publishing for WebGL targets, StackTrace is no longer maintained in Release compilation mode, which improves performance by about 1-2%. +- [fix] Fixes the bug where `Transform Enum::GetHashCode` did not change the variable type from `uintptr_t` to `int32_t` on the stack, leading to incorrect calculations when participating in subsequent numerical computations due to the parameter type being extended to 64 bits. +- [fix] Fixes the bug where a stack overflow was triggered by calling delegates extensively within interpreter functions. + +### Editor + +- [new] HybridCLRSettings adds two new options: `enableProfilerInReleaseBuild` and `enableStackTraceInWebGLReleaseBuild`. +- [change] Fixes the issue where an assertion failure occurred when switching from the WebGL platform to other platforms (no substantial impact). + +## 6.6.0 + +Release Date: 2024-8-12. + +### Runtime + +- [fix] Fixes the bug where `CustomAttribute` construction or namedArg includes a `typeof(T[])` parameter, causing a crash. +- [fix] Fixes the bug where `T[index].CallMethod()` throws an `ArrayTypeMismatchException` when `CallMethod` is an interface function of generic type T, and the array element is a subclass of T. +- [fix] Fixes the bug where `MethodBase.GetCurrentMethod` does not return the correct result. A new instinct instruction `MethodBaseGetCurrentMethod` is added. +- [fix] Fixes the bug where loading pdb on platforms like WebGL still does not display stack code line numbers. +- [fix] Fixes the bug where, after calling a sub-interpreter function and returning, logging prints the code line number of the called function due to `frame->ip` not being reset to `&ip`. +- [fix] Fixes the bug where calling a sub-interpreter function displays the line number of the next statement in the parent function's code line number due to `frame->ip` pointing to the next instruction. +- [merge] Merges il2cpp code from 2021.3.42f1 and 2022.3.41f1, fixing compilation errors caused by the new `il2cpp_codegen_memcpy_with_write_barrier` function in versions 2021.3.42f1 and 2022.3.40f1. + +## 6.5.0 + +Release Date: 2024-8-5. + +### Runtime + +- [new] Hot update function stacks for versions 2019-2020 can now correctly display code files and line numbers. +- [merge] Merges il2cpp changes from Unity versions 6000.0.1 to 6000.0.10 + +## 6.4.0 + +Release Date: 2024-7-25. + +### Runtime + +- [new] Supports loading dll and pdb symbol files with `Assembly.Load(byte[] assData, byte[] pdbData)`, displaying the correct code files and line numbers in function stacks when printing on versions 2021+. +- [fix] Fixes the bug where `InterpreterImage::GetEventInfo` and `GetPropertyInfo` might not initialize `method`, resulting in empty getter functions. +- [opt] Optimizes the order of function stacks printed by `StackTrace` and `UnityEngine.Debug`, displaying interpreter functions at the correct stack positions in most cases. +- [opt] Optimizes metadata memory. + +### Editor + +- [fix] Fixes the bug where `GenerateMethodBridge` did not consider `ClassLayout`, `Layout`, and `FieldOffset` factors when calculating equivalent classes. +- [fix] Fixes the bug where `PatchScriptingAssembliesJsonHook` throws an异常 when the `Library/PlayerDataCache` directory does not exist. + +## 6.3.0 + +Release Date: 2024-7-15. + +### Runtime + +- [opt] Significantly optimizes metadata memory, reducing memory usage by 15-40% compared to version 6.2.0. +- [fix] Fixes the bug where memory was not released for `insts` in `IRBasicBlock` during transformation, causing a memory leak approximately 0.7-1.6 times the size of the dll. +- [fix] Fixes the bug where `ClassFieldLayoutCalculator` caused a memory leak. +- [fix] Fixes the bug where `MetadataAllocT` incorrectly used `HYBRIDCLR_MALLOC` instead of `HYBRIDCLR_METADATA_MALLOC`. +- [opt] Optimizes the native stack size occupied by `Interpreter::Execute` to avoid stack overflow errors when nesting is too deep. + +### Editor + +- [fix] Fixes the bug where exporting an xcode project for Unity 2022 includes multiple ShellScript fragments, incorrectly deleting non-repeated fragments. +- [fix] Fixes the bug where the temporary directory name is `WinxinMiniGame{xxx}` when `TextureCompression` is not the default value on the WeChat Mini Games platform, causing the `scriptingassemblies.json` file to not be successfully modified. +- [fix] Fixes the bug where the WeChat Mini Games platform on the Unity Engine, due to the definition of both `UNITY_WEIXINMINIGAME` and `UNITY_WEBGL` macros, fails to find the `scriptingassemblies.json` file from the wrong path, resulting in a script missing bug at runtime. + +## 6.2.0 + +Release Date: 2024-7-1. + +### Runtime + +- [merge] Merges changes from versions 2021.3.27f1 to 2021.3.40f1. +- [opt] Optimizes metadata memory, reducing memory usage by 20-25%. +- [opt] Optimizes the implementation of `GetHashCode` for enum types, no longer generating GC. + +## 6.1.0 + +Release Date: 2024-6-17. + +### Runtime + +- [merge] Merges changes from versions 2022.3.23f1 to 2022.3.33f1, fixing incompatibility issues with version 2022.3.33. +- [new] Supports the new function return value Attribute added in version 2022.3.33. +- [fix] Fixes the bug where `FieldInfo` calling `GetFieldMarshaledSizeForField` crashes. + +### Editor + +- [fix] Upgrades the dnlib version, fixing the serious bug where `ModuleMD` saves dlls without setting the assembly-qualified `mscorlib` assembly types to the current assembly. +- [fix] Fixes the issue where `Generate/LinkXml` generates a `link.xml` that preserves all `UnityEngine.Debug`, causing compilation errors on iOS and visionOS platforms with Unity 2023 and higher versions. This bug is caused by Unity, and we temporarily solve this problem by ignoring the `UnityEngine.Debug` class when generating `link.xml`. + +## 6.0.0 + +Release Date: 2024-6-11. + +### Runtime + +- [new] Supports Unity 6000.x.y and Unity 2023.2.x versions. +- [refactor] Merges `ReversePInvokeMethodStub` into `MethodBridge`, and moves ReversePInvoke-related code from `MetadataModule` to `InterpreterModule`. +- [new] Supports MonoPInvokeCallback functions with parameters or return types as struct types. + +### Editor + +- [new] Supports Unity 6000.x.y and Unity 2023.2.x versions. +- [new] Supports MonoPInvokeCallback functions with parameters or return types as struct types. +- [new] Adds `GeneratedAOTGenericReferenceExcludeExistsAOTClassAndMethods`, which calculates hot update references to AOT generic types and functions, excluding those already existing in AOT, ultimately generating a more accurate list of supplementary metadata assembly programs. +- [fix] Fixes the bug where `CopyStrippedAOTAssemblies` class has compilation errors on some Unity versions that do not support visionOS. +- [fix] Fixes the bug where calculating the `CallingConvention` of `MonoPInvokeCallback` is incorrectly treated as Winapi if the delegate is defined in another assembly, resulting in an incorrect wrapper signature calculation. +- [fix] PatchScriptingAssemblyList.cs has compilation errors on Unity 2023+ WebGL platforms. +- [fix] Fixes the bug where calculating Native2Manager bridge functions does not consider MonoPInvokeCallback functions, leading to `UnsupportedNative2ManagedMethod` when calling C# hot update functions from Lua or other languages. +- [refactor] Merges `ReversePInvokeMethodStub` into `MethodBridge`, and moves ReversePInvoke-related code from `MetadataModule` to `InterpreterModule`. +- [opt] Checks if the development option during packaging is consistent with the current development option. Switching the development option after `Generate/All` and then packaging will cause serious crashes. +- [opt] `Generate/All` checks if HybridCLR is installed before generating. + +## 5.4.1 + +Release Date: 2024-5-30. + +### Editor + +- [new] Supports the visionOS platform. +- [fix]**[Serious]** Fixes the bug where calculating `MonoPInvokeCallback`'s `CallingConvention` incorrectly treats it as Winapi if the delegate is defined in another assembly, resulting in an incorrect wrapper signature calculation. +- [fix] Fixes the bug where the wrong Unity-iPhone.xcodeproj path is used on tvOS platforms, causing the project.pbxproj to not be found. + +## 5.4.0 + +Release Date: 2024-5-20. + +### Runtime + +- [new] ReversePInvoke supports CallingConvention. +- [fix] Fixes the bug where `calli`'s `argBasePtr=argIdx[0]` when the number of arguments is 0, due to `argIdxs` not being assigned, causing the function stack frame to point to the wrong location. +- [fix] Fixes the bug where `MetadataModule::GetReversePInvokeWrappe`'s `ComputeSignature` might deadlock. +- [fix] Fixes the bug where AOT base class virtual functions implementing hot update interface functions use `CallInterpVirtual`, causing runtime exceptions. +- [fix] Fixes the issue where some sub-instructions of the `PREFIX1` prefix instruction are missing and not sorted by instruction number in the Transform. +- [fix] Fixes the bug where the `no.{x}` prefix instruction is 3 bytes long but incorrectly treated as 2 bytes in the Transform. +- [fix] Fixes the bug where the `unaligned.{x}` prefix instruction is 3 bytes long but incorrectly treated as 2 bytes in the Transform. +- [opt] Removes unnecessary `INIT_CLASS` operations in `Interpreter_Execute`, as `PREPARE_NEW_FRAME_FROM_NATIVE` will always check. +- [opt] No longer caches MethodBody of non-generic functions, optimizing memory. +- [opt] **Optimizes supplementary metadata memory**, saving approximately 2.8 times the size of metadata dll memory. +- [refactor] Changes the type of the `_rawImage` field in `Image` from `RawImage` to `RawImage*`. + +### Editor + +- [new] ReversePInvoke supports CallingConvention. +- [fix] Fixes the bug where calculating the equivalence of structs by flattening and expanding them does not apply on some platforms. For example, struct A { uint8_t x; A2 y; } struct A2 { uint8_t x; int32_t y;}; and struct B { uint8_t x; uint8_t y; int32_t z; } are not equivalent under the x86_64 ABI. +- [fix] Fixes the bug where appending to an existing xcode project causes the 'Run Script' command to be duplicated the first time and subsequently fails to find --external-lib-il2-cpp, printing an error log. + +## 5.3.0 + +Release Date: 2024-4-22. + +### Runtime + +- [fix] Fixes the bug where MachineState::CollectFramesWithoutDuplicates incorrectly uses `hybridclr::metadata::IsInterpreterMethod` to remove hot update functions, leading to an increasingly long StackFrames list and an infinite loop when printing the stack. The implementation is adjusted to uniformly use `il2cpp::vm::StackTrace::PushFrame` and `PopFrame` for perfect interpreter stack printing. The downside is the increased overhead of maintaining the stack when calling interpreter functions. +- [fix] Fixes the serious bug where `StringUtils::Utf16ToUtf8` does not correctly handle `maxinumSize==0`, causing a significant overflow when converting strings of length 0 in `InterpreterImage::ConvertConstValue`. +- [fix] Fixes the bug where `_ReversePInvokeMethod_XXX` functions do not set `Il2CppThreadContext`, causing a crash when obtaining thread variables from native threads. +- [merge] Merges il2cpp changes from versions 2021.3.34 to 2021.3.37f1. +- [merge] Merges il2cpp changes from versions 2022.3.19 to 2022.3.23f1. + +### Editor + +- [fix] Fixes the bug where exporting a tvOS project does not modify xcode project settings, causing packaging to fail. +- [fix] Fixes the bug where building for tvOS targets does not copy the pruned AOT dll, causing bridge function generation to fail. +- [fix] Solves the issue where the locationPathName generated by `StripAOTDllCommand` is not standardized, causing incompatibility with some plugins like the Embedded Browser. +- [fix] Fixes the bug where deleting the `TUANJIE_2022` macro in Unity Engine 1.1.0 does not copy the pruned AOT assembly. +- [fix] Fixes the bug where `_ReversePInvokeMethod_XXX` functions do not set `Il2CppThreadContext`, causing a crash when obtaining thread variables from native threads. +- [fix] Fixes the bug where iOS platform mono-related header files are not found when the development build option is enabled. + +## 5.2.1 + +Release Date: 2024-4-7. + +### Runtime + +- [fix] Fixes the bug where stack logs are not printed on the WebGL platform. +- [fix] Fixes the bug where `RuntimeConfig::GetRuntimeOption` incorrectly returns `s_threadFrameStackSize` for `InterpreterThreadExceptionFlowSize`. + +### Editor + +- [opt] Sets `mod.EnableTypeDefFindCache = true` in `LoadModule`, reducing the time to calculate bridge functions to one-third of the original. +- [fix] Fixes the bug where renaming the xcode project file to `Tuanjie-iPhone.xcodeproj` when exporting for the Unity Engine platform causes xcode project construction to fail. + +## 5.2.0 + +Release Date: 2024-3-25. + +### Runtime + +- [new] Supports the Unity Engine. +- [new] Supports function pointers, supporting IL2CPP_TYPE_FNPTR type. +- [fix] Fixes the bug where the `SetMdArrElementVarVar_ref` instruction does not SetWriteBarrier. +- [fix] Fixes the bug where `InvokeSingleDelegate` crashes when calling a generic function without supplementary metadata. +- [fix] Fixes the bug where `InterpreterDelegateInvoke` crashes when calling a delegate pointing to a generic function without supplementary metadata. +- [fix] Fixes the bug where `RawImage::GetBlobFromRawIndex` fails when the BlobStream is empty. +- [change] Refactorizes the metadata index design, allowing up to 3 64M dlls, 16 16M dlls, 64 4M dlls, and 255 1M dlls to be allocated. + +### Editor + +- [new] Supports the Unity Engine. +- [fix] Fixes the bug where `GenericArgumentContext` does not support `ElementType.FnPtr`. +- [change] Adds the `[Preserve]` attribute to RuntimeApi to prevent it from being pruned. + +## 5.1.0 + +Release Date: 2024-2-26. + +### Runtime + +- [fix] Fixes the runtime error caused by not implementing `System.ByReference`1's .ctor and get_Value functions in 2021, where il2cpp runs normally through special instinct functions. +- [opt] Optimizes metadata loading by delaying the loading of some metadata, reducing the execution time of `Assembly::Load` by approximately 30%. +- [change] Changes `tempRet` from a local variable in `Interpreter::Execute` to a local variable in `CallDelegateInvoke_xxx`, reducing the possibility of stack overflow when nesting is too deep. + +## 5.0.0 + +Release Date: 2024-1-26. + +### Runtime + +- [new] Restores support for 2019. +- [fix] Fixes the bug where dlls are not loaded in dependency order, and since the assembly list at the time of image creation is cached, if dependent assemblies are loaded after this assembly, delayed access may result in `TypeLoadedException` due to not being in the cached assembly list. + +### Editor + +- [new] Restores support for 2019. +- [new] Supports building 2019 on the iOS platform in source form. +- [new] Adds AOTAssemblyMetadataStripper to remove non-generic function metadata from AOT dlls. +- [new] Adds MissingMetadataChecker to check for missing types or function metadata. +- [opt] Optimizes AOTReference calculations; if all generic parameters of a generic are class-constrained, they are not added to the set of metadata that needs to be supplemented. +- [change] Makes some adjustments to support the Unity Engine (note that the il2cpp_plus branch supporting the Unity Engine has not been made public). + +## 4.0.15 + +Release Date: 2024-1-2. + +### Runtime + +- [fix] Fixes the serious bug where the size of the instance of a not fully instantiated generic class is calculated as `sizeof(void*)`, resulting in an invalid and excessively large instance. This causes an error when using the generic base class instance to overwrite the instance type value set during `LayoutFieldsLocked` in `UpdateInstanceSizeForGenericClass`. +- [change] Supports printing hot update stacks, although the order is not quite correct. +- [change] Replaces IL2CPP_MALLOC with HYBRIDCLR_MALLOC and similar allocation functions. +- [refactor] Refactorizes the Config interface to统一ly retrieve and set options through `GetRuntimeOption` and `SetRuntimeOption`. +- [opt] Removes unnecessary memset operations on structures for `NewValueTypeVar` and `NewValueTypeInterpVar` instructions. + +### Editor + +- [fix] Fixes the bug where entering `-nullable:enable` in Additional Compiler Arguments throws an `InvalidCastException` in the Editor. Reported at https://github.com/focus-creative-games/hybridclr/issues/116 +- [fix] Fixes the error: `BuildFailedException: Build path contains a project previously built without the "Create Visual Studio Solution"` +- [opt] Optimizes bridge function generation by mapping isomorphic structs to the same structure, reducing the number of bridge functions by 30-35%. +- [change] `StripAOTDllCommand` no longer sets the `BuildScriptsOnly` option when exporting. +- [change] Adjusts the display content of the Installer window. +- [refactor] Centralizes the functionality of setting hybridclr parameters in RuntimeApi through `GetRuntimeOption` and `SetRuntimeOption` functions. + +## 4.0.14 + +Release Date: 2023-12-11. + +### Runtime + +- [fix] Fixes the bug where optimizing the `box; brtrue|brfalse` sequence unconditionally converts to an unconditional branch statement when the type is a class or nullable type. +- [fix] Fixes the bug where `ClassFieldLayoutCalculator` does not release value objects in each key-value pair of `_classMap`, causing a memory leak. +- [fix] Fixes the bug where calculating the native_size of a struct with `ExplicitLayout` is incorrect. +- [fix] Fixes the bug where when there are virtual functions with identical signatures and virtual generic functions, the override calculation does not consider the generic signature, incorrectly returning a non-matching function, resulting in an incorrect vtable. +- [fix][2021] Fixes the bug where when the faster (smaller) build option is enabled, some fully generic shared AOT functions do not use supplementary metadata to set function pointers, causing errors when called. + +## 4.0.13 + +Release Date: 2023-11-27. + +### Runtime + +- [fix] Fixes the bug where `ConvertInvokeArgs` might pass non-aligned args, causing `CopyStackObject` to crash on platforms like armv7 that require memory alignment. +- [fix] Fixes the serious bug where calculating `ClassFieldLayout` when the size is specified by `StructLayout`. +- [fix] Fixes the bug where instructions like `bgt` do not double-negate the judgment, causing incorrect branch execution when comparing floating-point numbers with NaN due to不对称性. +- [fix] Fixes the serious bug where `Class::FromGenericParameter` incorrectly sets `thread_static_fields_size=-1`, causing ThreadStatic memory allocation for it. +- [opt] Allocates `Il2CppGenericInst`统一ly using `MetadataCache::GetGenericInst` to allocate unique pool objects, optimizing memory allocation. +- [opt] Since some Il2CppGenericInst in the Interpreter uses `MetadataCache::GetGenericInst` uniformly, compare `Il2CppGenericContext` by directly comparing class_inst and method_inst pointers. + +### Editor + +- [fix] Fixes the bug where pruning aot dll results in an exception when generating bridge functions if netstandard is referenced. +- [fix] Fixes the bug where unusual field names result in compilation errors in the generated bridge function code files. +- [change] Removes the unnecessaryDatas~/Templates directory, using the original files as templates directly. +- [refactor] Refactorizes `AssemblyCache` and `AssemblyReferenceDeepCollector` to eliminate redundant code. + +## 4.0.12 + +Release Date: 2023-11-02. + +### Editor + +- [fix] Fixes the bug in `BashUtil.RemoveDir` causing Installer installation to fail. + +## 4.0.11 + +Release Date: 2023-11-02. + +### Runtime + +- [fix] Fixes the bug where when full generic sharing is enabled, for some `MethodInfo`, since `methodPointer` and `virtualMethodPointer` use the interpreter function with supplementary metadata, while `invoker_method` remains in the call form supporting full generic sharing, causing `invoker_method` to mismatch with `methodPointer` and `virtualMethodPointer`. +- [fix] Fixes the bug where `Il2CppGenericContextCompare` only compares inst pointers, causing a large number of duplicate generic functions in the hot update module. +- [fix] Fixes the bug where `MethodInfo` is not correctly set when full generic sharing is enabled. + +### Editor + +- [new] Checks if the currently installed libil2cpp version matches the package version to avoid issues when upgrading the package without reinstalling. +- [new] `Generate` supports netstandard. +- [fix] Fixes the bug where `ReversePInvokeWrap` generates unnecessarily, parsing referenced dlls, causing parsing errors if aot dll references netstandard. +- [fix] Fixes the bug where `BashUtil.RemoveDir` occasionally fails to delete directories. Adds multiple retries. +- [fix] Fixes the bug where bridge function calculation does not reduce function parameter types, resulting in multiple functions with the same signature. + +## 4.0.10 + +Release Date: 2023-10-12. + +### Runtime + +- [merge][il2cpp] Merges il2cpp changes from versions 2022.3.10 to 2022.3.11f1, fixing incompatibility issues with version 2022.3.11. + +## 4.0.9 + +Release Date: 2023-10-11. + +### Runtime + +- [merge][il2cpp][fix] Merges il2cpp changes from versions 2021.3.29 to 2021.3.31f1, fixing incompatibility issues with version 2021.3.31. +- [merge][il2cpp] Merges il2cpp changes from versions 2022.3.7 to 2022.3.10f1. + +### Editor + +- [fix] Fixes the compilation error with `AddLil2cppSourceCodeToXcodeproj2022OrNewer` on the iOS platform for Unity 2022 versions. + +## 4.0.8 + +Release Date: 2023-10-10. + +### Runtime + +- [fix] Fixes the bug where calculating the bridge function signature for value type generic bridge functions incorrectly replaces the value type generic parameter type with the signature, resulting in an inconsistent signature with the Editor calculation. +- [fix][refactor] Changes RuntimeApi related functions from PInvoke to InternalCall, solving the issue of reloading libil2cpp.a when calling RuntimeApi on Android platforms. + +### Editor + +- [refactor] Changes RuntimeApi related functions from PInvoke to InternalCall . +- [refactor] Adjusts some non-standard namespace names in the HybridCLR.Editor module. + +## 4.0.7 + +Release Date: 2023-10-09. + +### Runtime + +- [fix] Fixes the bug where `initobj` calls `CopyN`, but `CopyN` does not consider object memory alignment, which may cause unaligned access exceptions on platforms like 32-bit. +- [fix] Fixes the bug where calculating the bridge function signature for not fully instantiated generic functions crashes. +- [fix] Fixes the bug where `GenericMethod::CreateMethodLocked` has issues when the Il2cpp code generation option is faster (smaller) for versions 2021 and 2022. +- [remove] Removes all array-related instructions with int64_t indices to simplify the code. +- [remove] Removes the `ldfld_xxx_ref` series of instructions. + +### Editor + +- [fix] Fixes the bug where generating bridge functions does not generate bridge functions for an aot assembly if the hot update assembly does not directly reference any code, resulting in a `NotSupportNative2Managed` exception. +- [fix] Fixes the bug where copying files fails on Mac due to excessively long paths. +- [fix] Fixes the bug where publishing for PS5 targets does not process `ScriptingAssemblies.json`. +- [change] Clears the pruned aot dll directory when packaging. + +## 4.0.6 + +Release Date: 2023-09-26. + +### Runtime + +- [fix] Fixes the bug with versions 2021 and 2022 when full generic sharing is enabled. +- [fix] Fixes the bug where loading a PlaceHolder Assembly does not increase `assemblyVersion`, causing `Assembly::GetAssemblies()` to incorrectly obtain an old assembly list. + +## 4.0.5 + +Release Date: 2023-09-25. + +### Runtime + +- [fix] Fixes the bug where `Transform` does not destruct `pendingFlows`, causing a memory leak. +- [fix] Fixes the bug where `SetMdArrElement` does not distinguish between structures with and without ref. +- [fix] Fixes the bug where `CpobjVarVAr_WriteBarrier_n_4` does not set the size. +- [fix] Fixes the bug where calculating interface member function slots does not consider static and similar functions. +- [fix] Fixes the bug where `ExplicitLayout` is not set for layout.alignment in version 2022, resulting in a size of 0. +- [fix] Fixes the bug where `InterpreterInvoke` in full generic sharing may have inconsistent `methodPointer` and `virtualMethodPointer` for class types, causing an error in incrementing the this pointer by 1. +- [fix] Fixes the bug where `ldobj` does not expand data into an int when T is a type like byte with a size less than 4. +- [fix] Fixes the bug where `CopySize` does not consider memory alignment issues. +- [opt] Optimizes `stelem` when the element is a larger struct, unifying it as a structure containing ref. +- [opt] Adjusts the default memory block size of `TemporaryMemoryArena` from 1M to 8K. +- [opt] Changes `Assembly::GetAllAssemblies()` in `Image::Image` to `Assembly::GetAllAssemblies(AssemblyVector&)`, avoiding the creation of an assembly snapshot and preventing unnecessary memory leaks. + +### Editor + +- [fix] Fixes the bug where `DllImport` for the StandaloneLinux platform has incorrect dllName and pruned dll path errors. +- [change] For Unity versions with minor incompatibility, installation is no longer prohibited, but a warning is displayed instead. +- [fix] Fixes the bug where `MetaUtil.ToShareTypeSig` calculates `Ptr` and `ByRef` as `IntPtr` in bridge function calculations, which should correctly be `UIntPtr`. + +## 4.0.4 + +Release Date: 2023-09-11. + +### Runtime + +- [new][platform] Fully supports all platforms, including UWP and PS5. +- [fix][serious] Fixes the bug where calculating the bridge function signature for interpreter parts of enum types is incorrect. +- [fix] Fixes compilation errors on some platforms. +- [fix] Fixes the bug where converting STOBJ instructions does not correctly handle incremental GC. +- [fix] Fixes the bug where the `StindVarVar_ref` instruction does not correctly set WriteBarrier. +- [fix] Fixes the thread safety issue where `GenericMethod::CreateMethodLocked` calls `vm::MetadataAllocGenericMethod()` without holding the `s_GenericMethodMutex` lock in version 2020. + +### Editor + +- [fix] Fixes the bug where `AddLil2cppSourceCodeToXcodeproj2021OrOlder` includes two ThreadPool.cpp files in different directories, causing compilation errors in Unity 2020. +- [fix] Fixes the bug where obtaining `BuildGroupTarget` from `EditorUserBuildSettings.selectedBuildTargetGroup` is incorrect. +- [fix] `StripAOTDllCommand` generates AOT dlls with the current Player settings to avoid serious mismatches between supplementary metadata and bridge function generation when packaging with development enabled. +- [change] To better support all platforms, adjusts the implementation of dllName in RuntimeApi.cs to default to `__Internal`. +- [change] To better support all platforms, all AOT dll pruning since 2021 is done through MonoHook copying. + +## 4.0.3 + +Release Date: 2023-08-31. + +### Editor + +- [fix] Fixes the bug in bridge function calculation. + +## 4.0.2 + +Release Date: 2023-08-29. + +### Runtime + +- [fix][serious] Fixes the bug in `LdobjVarVar_ref` instruction. This bug was introduced by incremental GC code. +- [fix] Fixes the bug where `ResolveField` obtaining a Field as nullptr is not handled, causing a crash. +- [fix] Fixes the bug where AOT and interpreter interface explicitly implement parent interface functions are not correctly handled. + +## 4.0.1 + +Release Date: 2023-08-28. + +### Runtime + +- [fix] Fixes the compilation error when incremental GC is enabled in version 2020. + +## 4.0.0 + +Release Date: 2023-08-28. + +### Runtime + +- [new] Supports incremental GC. +- [refactor] Refactorizes bridge functions to fully support all platforms supported by il2cpp. +- [opt] Significantly optimizes Native2Managed direction parameter passing. + +### Editor + +- [change] Removes incremental GC option checks. +- [refactor] Refactorizes bridge function generation. + +## 3.4.2 + +Release Date: 2023-08-14. + +### Runtime + +- [fix] Fixes the bug in `RawImage::LoadTables` reading `_4byteGUIDIndex`. +- [version] Supports version 2022.3.7. +- [version] Supports version 2021.3.29. + +### Editor + +- [fix] Fixes the bug where calculating AOTGenericReference does not consider generic calls on generics, resulting in fewer calculated generics and supplementary metadata. + +## 3.4.1 + +Release Date: 2023-07-31. + +### Runtime + +- [fix] Fixes the memory visibility issue in `InitializeRuntimeMetadata`. +- [fix] Fixes the bug where `CustomAttribute` does not correctly handle parent NamedArg, causing a crash. +- [opt] Optimizes the code for Transform Instinct instructions, quickly looking up in the HashMap instead of matching one by one. + +### Editor + +- [fix] Fixes the bug where `FilterHotFixAssemblies` only compares the tail of the assembly name, causing an assembly to be unexpectedly filtered if it matches the tail of an AOT assembly. +- [change] Checks that the assembly name in the hot update assembly list configuration in Settings is not empty. + +## 3.4.0 + +Release Date: 2023-07-17. + +### Runtime + +- [version] Supports versions 2021.3.28 and 2022.3.4. +- [opt] Removes unnecessary memset after allocating `_StackBase` in `MachineState::InitEvalStack`. +- [fix] Fixes the exception mechanism bug. +- [fix] Fixes the bug where `CustomAttribute` does not support Type[] type parameters. +- [fix] Fixes the issue where the new string(xxx) syntax is not supported. +- [refactor] Refactorizes VTableSetup implementation. +- [fix] Fixes the bug where functions explicitly implementing parent interfaces in subinterfaces are not calculated. +- [opt] Lazily initializes `CustomAttributeData` instead of initializing all at load time, significantly reducing `Assembly.Load` time. +- [fix] Fixes the bug where new byte[]{a,b,c...} initialization of longer byte[] data returns incorrect data in version 2022. + +### Editor + +- [fix] Fixes the bug where calculating bridge functions does not consider Native2Managed calls that may be included in generic class member functions. +- [change] The default output paths for link.xml and AOTGenericReferences.cs are changed to HybridCLRGenerate to avoid confusion with the top-level HybridCLRData. +- [fix] Fixes the bug where the include path in the lump file generated on Windows uses \ as the directory separator, causing path not found errors when synchronized to Mac. +- [refactor] Refactorizes the Installer. + +## 3.3.0 + +Release Date: 2023-07-03. + +### Runtime + +- [fix] Fixes the bug where memory allocated by localloc is not released. +- [change] `MachineState` uses RegisterRoot to register the execution stack, avoiding GC scanning of the entire stack. +- [opt] Optimizes the performance of Managed2NativeCallByReflectionInvoke by calculating the parameter passing method in advance. +- [refactor] Refactorizes ConvertInvokeArgs. + +### Editor + +- [fix] Fixes the bug where compiling libil2cpp.a for 2020-2021 does not include brotli-related code files, resulting in compilation errors. +- [fix] Fixes the bug where exporting an xcode project includes absolute paths, causing path not found errors when compiled on other machines. +- [fix] Solves the instability issues in generating LinkXml, MethodBridge, AOTGenericReference, and ReversePInvokeWrap. +- [fix] Fixes the exception when opening the Installer with an incompatible version. +- [change] When hybridclr is disabled, packaging iOS no longer modifies the exported xcode project. + +## 3.2.1 + +### Runtime + +- [fix] Fixes the bug where il2cpp TypeNameParser does not remove escape characters '\' from type names, causing nested child types to not be found. + +### Editor + +- [new] The Installer interface adds display of package version. +- [new] CompileDll adds MacOS, Linux, and WebGL targets. +- [fix] Fixes the help documentation link errors after refactoring the documentation site. +- [change] Adds using qualifiers to Analyzer to resolve compilation conflicts with project types that have the same name. + +## 3.2.0 + +### Runtime + +- [fix] Fixes the bug where if an Assembly is not in the PlaceHolder, and there is no interpreter stack, `Class::resolve_parse_info_internal` cannot find the type due to not being in the Assembly list. + +### Editor + +- [new] Supports packaging iOS directly from source code, no longer needing to compile libil2cpp.a separately. +- [opt] Optimizes error prompts for incompatible versions, no longer throwing exceptions, but displaying "incompatible with the current version". + +## 3.1.1 + +### Runtime + +- [fix] Fixes the bug where InterpreterModule::Managed2NativeCallByReflectionInvoke calls value type member functions in 2021 and higher versions, with an extra this=this-1 operation. +- [fix] Fixes the bug where parsing CustomAttribute Enum[] type fields. +- [fix] Fixes the bug where invoking the Invoke function of a closed Delegate via reflection in 2021 and higher versions does not repair the target pointer. + +### Editor + +- [fix] Fixes compilation errors for Win32, Android32, and WebGL platforms. +- [fix] Fixes the bug where calculating bridge functions does not consider supplementary metadata generic instantiation, which may access some non-public functions, resulting in fewer necessary bridge functions being generated. +- [opt] When generating AOTGenericReferences, the supplementary metadata assembly list is changed from comments to List lists for easy direct use in code. +- [change] CheckSettings no longer automatically sets Api Compatible Level. + +## 3.1.0 + +### Runtime + +- [rollback] Reverts support for Unity 2020.3.x. +- [fix] Fixes the WebGL platform ABI bug. + +### Editor + +- [rollback] Reverts support for Unity 2020.3.x. + +## 3.0.3 + +### Runtime + +- [fix] Fixes the bug where Enum::GetValues returns incorrect values. + +## 3.0.2 + +### Runtime + +- [fix] Fixes the bug where creating a memory snapshot in Memory Profiler crashes. + +### Editor + +- [remove] Removes the `HybridCLR/CreateAOTDllSnapshot` menu. + +## 3.0.1 + +### Runtime + +- [new] Supports version 2022.3.0. + +## 3.0.0 + +### Runtime + +- [fix] Fixes the bug where accessing CustomData fields and values is not supported. +- [remove] Removes support for 2019 and 2020 versions. + +### Editor + +- Changes the package name to com.code-philosophy.hybridclr. +- Removes the UnityFS plugin. +- Removes the Zip plugin. +- Adjusts the HybridCLR menu location. + +## 2.4.2 + +### Runtime + +- [version] Supports 2020.3.48, the last 2020 LTS version. +- [version] Supports 2021.3.25. + +## 2.4.1 + +### Runtime + +### Editor + +- [fix] Fixes the遗漏 RELEASELOG.md.meta file issue. + +## 2.4.0 + +### Runtime + +### Editor + +- [new] CheckSettings checks ScriptingBackend and ApiCompatibleLevel, switching to the correct values. +- [new] Adds MsvcStdextWorkaround.cs to solve stdext compilation errors in 2020 vs. +- [fix] Fixes the bug where calculating bridge function signatures for structs containing only one float or double field is incorrect on arm64. + +## 2.3.1 + +### Runtime + +### Editor + +- [fix] Fixes the bug where copying libil2cpp locally still downloads and installs from the repository. + +## 2.3.0 + +### Runtime + +### Editor + +- [new] The Installer supports copying modified libil2cpp from a local directory. +- [fix] Fixes the bug where the MonoBleedingEdge subdirectory in version 2019 includes files with excessively long paths, causing the Installer to fail when copying files. + + + + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta new file mode 100644 index 00000000..47abd216 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8e53ce54bd8e88c4785c625555308dba +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime.meta new file mode 100644 index 00000000..d5e99d32 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6d5e365e1b7d9742bee023ea54b31f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs new file mode 100644 index 00000000..f2448566 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs @@ -0,0 +1,10 @@ + +namespace HybridCLR +{ + public enum HomologousImageMode + { + Consistent, + SuperSet, + } +} + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta new file mode 100644 index 00000000..dde78bfd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f0351553ad90e74aa586746b5965ded +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef new file mode 100644 index 00000000..98f5d3e8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "HybridCLR.Runtime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta new file mode 100644 index 00000000..c73a6d48 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 13ba8ce62aa80c74598530029cb2d649 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs new file mode 100644 index 00000000..93d4b386 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs @@ -0,0 +1,16 @@ + +namespace HybridCLR +{ + public enum LoadImageErrorCode + { + OK = 0, + BAD_IMAGE, // invalid dll file + NOT_IMPLEMENT, // not implement feature + AOT_ASSEMBLY_NOT_FIND, // AOT assembly not found + HOMOLOGOUS_ONLY_SUPPORT_AOT_ASSEMBLY, // can not load supplementary metadata assembly for non-AOT assembly + HOMOLOGOUS_ASSEMBLY_HAS_LOADED, // can not load supplementary metadata assembly for the same assembly + INVALID_HOMOLOGOUS_MODE, // invalid homologous image mode + PDB_BAD_FILE, // invalid pdb file + }; +} + diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta new file mode 100644 index 00000000..4f770294 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c7d5b71981fba643b4c21ed01bcb675 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs new file mode 100644 index 00000000..33083139 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class ReversePInvokeWrapperGenerationAttribute : Attribute + { + public int ReserveWrapperCount { get; } + + public ReversePInvokeWrapperGenerationAttribute(int reserveWrapperCount) + { + ReserveWrapperCount = reserveWrapperCount; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta new file mode 100644 index 00000000..a46bd5a7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f99dd22d9d81b2540b4663b3bcdf0a79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs new file mode 100644 index 00000000..1ee67bb0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine.Scripting; + +namespace HybridCLR +{ + [Preserve] + public static class RuntimeApi + { + /// + /// load supplementary metadata assembly + /// + /// + /// + /// +#if UNITY_EDITOR + public static unsafe LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode) + { + return LoadImageErrorCode.OK; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode); +#endif + + /// + /// prejit method to avoid the jit cost of first time running + /// + /// + /// return true if method is jited, return false if method can't be jited + /// +#if UNITY_EDITOR + public static bool PreJitMethod(MethodInfo method) + { + return false; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool PreJitMethod(MethodInfo method); +#endif + + /// + /// prejit all methods of class to avoid the jit cost of first time running + /// + /// + /// return true if class is jited, return false if class can't be jited +#if UNITY_EDITOR + public static bool PreJitClass(Type type) + { + return false; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool PreJitClass(Type type); +#endif + + /// + /// get the maximum number of StackObjects in the interpreter thread stack (size*8 represents the final memory size occupied + /// + /// + public static int GetInterpreterThreadObjectStackSize() + { + return GetRuntimeOption(RuntimeOptionId.InterpreterThreadObjectStackSize); + } + + /// + /// set the maximum number of StackObjects for the interpreter thread stack (size*8 represents the final memory size occupied) + /// + /// + public static void SetInterpreterThreadObjectStackSize(int size) + { + SetRuntimeOption(RuntimeOptionId.InterpreterThreadObjectStackSize, size); + } + + + /// + /// get the number of interpreter thread function frames (sizeof(InterpreterFrame)*size represents the final memory size occupied) + /// + /// + public static int GetInterpreterThreadFrameStackSize() + { + return GetRuntimeOption(RuntimeOptionId.InterpreterThreadFrameStackSize); + } + + /// + /// set the number of interpreter thread function frames (sizeof(InterpreterFrame)*size represents the final memory size occupied) + /// + /// + public static void SetInterpreterThreadFrameStackSize(int size) + { + SetRuntimeOption(RuntimeOptionId.InterpreterThreadFrameStackSize, size); + } + + +#if UNITY_EDITOR + + private static readonly Dictionary s_runtimeOptions = new Dictionary(); + + /// + /// set runtime option value + /// + /// + /// + public static void SetRuntimeOption(RuntimeOptionId optionId, int value) + { + s_runtimeOptions[optionId] = value; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void SetRuntimeOption(RuntimeOptionId optionId, int value); +#endif + + /// + /// get runtime option value + /// + /// + /// +#if UNITY_EDITOR + public static int GetRuntimeOption(RuntimeOptionId optionId) + { + if (s_runtimeOptions.TryGetValue(optionId, out var value)) + { + return value; + } + return 0; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern int GetRuntimeOption(RuntimeOptionId optionId); +#endif + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta new file mode 100644 index 00000000..6ed5cdc6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d58bdc22b6d6b54ab6791baf16a0a3d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs new file mode 100644 index 00000000..25c34178 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs @@ -0,0 +1,12 @@ +namespace HybridCLR +{ + public enum RuntimeOptionId + { + InterpreterThreadObjectStackSize = 1, + InterpreterThreadFrameStackSize = 2, + ThreadExceptionFlowSize = 3, + MaxMethodBodyCacheSize = 4, + MaxMethodInlineDepth = 5, + MaxInlineableMethodBodySize = 6, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta new file mode 100644 index 00000000..9c7181ee --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64be598b47302644a96013c74d945653 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/package.json b/UnityProject/Packages/com.code-philosophy.hybridclr/package.json new file mode 100644 index 00000000..47508adf --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.code-philosophy.hybridclr", + "version": "8.3.0", + "displayName": "HybridCLR", + "description": "HybridCLR is a fully featured, zero-cost, high-performance, low-memory solution for Unity's all-platform native c# hotupdate.", + "category": "Scripting", + "documentationUrl": "https://hybridclr.doc.code-philosophy.com/#/", + "changelogUrl": "https://hybridclr.doc.code-philosophy.com/#/other/changelog", + "licensesUrl": "https://github.com/focus-creative-games/hybridclr_unity/blob/main/LICENSE", + "keywords": [ + "HybridCLR", + "hotupdate", + "hotfix", + "focus-creative-games", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "hybridclr@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.hybridclr/package.json.meta b/UnityProject/Packages/com.code-philosophy.hybridclr/package.json.meta new file mode 100644 index 00000000..65ee9862 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.hybridclr/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8ea8eca3a387d9d4988a2fca1036f2e7 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor.meta new file mode 100644 index 00000000..e4fe3a4d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2326b426d539e084dbddf7f7c23ed1bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf.meta new file mode 100644 index 00000000..d42ac4f6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 241df8eaf3a34dc47a0873c37ddb2695 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs new file mode 100644 index 00000000..8b349974 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs @@ -0,0 +1,263 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; +using UnityEngine; + +namespace Obfuz.Conf +{ + public interface IRule + { + void InheritParent(T parentRule); + } + + + public interface IMethodRule where R : IRule + { + string Name { get; set; } + NameMatcher NameMatcher { get; set; } + + R Rule { get; set; } + } + + public abstract class MethodRuleBase : IMethodRule where R : IRule + { + public string Name { get; set; } + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + } + + public interface ITypeRule where T : IMethodRule where R : IRule + { + string Name { get; set; } + + NameMatcher NameMatcher { get; set; } + + R Rule { get; set; } + + List Methods { get; set; } + } + + public abstract class TypeRuleBase : ITypeRule where T : IMethodRule where R : IRule + { + public string Name { get; set; } + + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + + public List Methods { get; set; } + } + + public interface IAssemblyRule where TType : ITypeRule where TMethod : IMethodRule where TRule : IRule + { + string Name { get; set; } + + TRule Rule { get; set; } + + List Types { get; set; } + } + public abstract class AssemblyRuleBase : IAssemblyRule where TType : ITypeRule where TMethod : IMethodRule where TRule : IRule + { + public string Name { get; set; } + + public TRule Rule { get; set; } + + public List Types { get; set; } + } + + public class XmlAssemblyTypeMethodRuleParser + where TMethod : IMethodRule, new() + where TType : ITypeRule, new() + where TAssembly : IAssemblyRule, new() + where TRule : IRule, new() + { + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly Func _ruleParser; + private readonly Action _unknownNodeTypeHandler; + private readonly Dictionary _assemblySpecs = new Dictionary(); + + public XmlAssemblyTypeMethodRuleParser(IEnumerable toObfuscatedAssemblyNames, Func ruleParser, Action unknownNodeTypeHandler) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _ruleParser = ruleParser; + _unknownNodeTypeHandler = unknownNodeTypeHandler; + } + + public Dictionary AssemblySpecs => _assemblySpecs; + + public void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + Debug.Log($"ConfigurableObfuscationPolicy::LoadConfig {configFile}"); + var doc = new XmlDocument(); + doc.Load(configFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {configFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "assembly": + { + TAssembly assSpec = ParseAssembly(configFile, ele); + _assemblySpecs.Add(assSpec.Name, assSpec); + break; + } + default: + { + if (_unknownNodeTypeHandler == null) + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + _unknownNodeTypeHandler(configFile, ele); + break; + } + } + } + } + + private TAssembly ParseAssembly(string configFile, XmlElement ele) + { + var assemblySpec = new TAssembly(); + string name = ele.GetAttribute("name"); + if (!_toObfuscatedAssemblyNames.Contains(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} isn't in toObfuscatedAssemblyNames"); + } + if (_assemblySpecs.ContainsKey(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} is duplicated"); + } + assemblySpec.Name = name; + assemblySpec.Rule = _ruleParser(configFile, ele); + + var types = new List(); + assemblySpec.Types = types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(configFile, childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TType ParseType(string configFile, XmlElement element) + { + var typeSpec = new TType(); + + string name = element.GetAttribute("name"); + typeSpec.Name = name; + typeSpec.NameMatcher = new NameMatcher(name); + typeSpec.Rule = _ruleParser(configFile, element); + + var methods = new List(); + typeSpec.Methods = methods; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "method": + { + methods.Add(ParseMethod(configFile, ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private TMethod ParseMethod(string configFile, XmlElement element) + { + var methodSpec = new TMethod(); + string name = element.GetAttribute("name"); + methodSpec.Name = name; + methodSpec.NameMatcher = new NameMatcher(name); + methodSpec.Rule = _ruleParser(configFile, element); + return methodSpec; + } + + public TRule GetMethodRule(MethodDef method, TRule defaultRule) + { + var assemblyName = method.DeclaringType.Module.Assembly.Name; + if (!_assemblySpecs.TryGetValue(assemblyName, out var assSpec)) + { + return defaultRule; + } + string declaringTypeName = method.DeclaringType.FullName; + foreach (var typeSpec in assSpec.Types) + { + if (typeSpec.NameMatcher.IsMatch(declaringTypeName)) + { + foreach (var methodSpec in typeSpec.Methods) + { + if (methodSpec.NameMatcher.IsMatch(method.Name)) + { + return methodSpec.Rule; + } + } + return typeSpec.Rule; + } + } + return assSpec.Rule; + } + + public void InheritParentRules(TRule defaultRule) + { + foreach (TAssembly assSpec in _assemblySpecs.Values) + { + assSpec.Rule.InheritParent(defaultRule); + foreach (TType typeSpec in assSpec.Types) + { + typeSpec.Rule.InheritParent(assSpec.Rule); + foreach (TMethod methodSpec in typeSpec.Methods) + { + methodSpec.Rule.InheritParent(typeSpec.Rule); + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta new file mode 100644 index 00000000..84f5aa9b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlAssemblyTypeMethodRuleParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36a3e142db81f6d4bb54938525e31973 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs new file mode 100644 index 00000000..5cef8c98 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs @@ -0,0 +1,203 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.Conf +{ + + + + public class XmlFieldRuleParser where R : class, new() + { + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly Func _ruleParser; + private readonly Action _unknownNodeTypeHandler; + private readonly Dictionary _assemblySpecs = new Dictionary(); + + + private class FieldSpec + { + public string Name { get; set; } + public NameMatcher NameMatcher { get; set; } + + public R Rule { get; set; } + } + + private class TypeSpec + { + public string Name { get; set; } + + public NameMatcher NameMatcher { get; set; } + + public List Fields { get; set; } + } + + private class AssemblySpec + { + public string Name { get; set; } + + public List Types { get; set; } + } + + public XmlFieldRuleParser(IEnumerable toObfuscatedAssemblyNames, Func ruleParser, Action unknownNodeTypeHandler) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _ruleParser = ruleParser; + _unknownNodeTypeHandler = unknownNodeTypeHandler; + } + + public void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + var doc = new XmlDocument(); + doc.Load(configFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {configFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "assembly": + { + AssemblySpec assSpec = ParseAssembly(configFile, ele); + _assemblySpecs.Add(assSpec.Name, assSpec); + break; + } + default: + { + if (_unknownNodeTypeHandler == null) + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + _unknownNodeTypeHandler(configFile, ele); + break; + } + } + } + } + + private AssemblySpec ParseAssembly(string configFile, XmlElement ele) + { + var assemblySpec = new AssemblySpec(); + string name = ele.GetAttribute("name"); + if (!_toObfuscatedAssemblyNames.Contains(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} isn't in toObfuscatedAssemblyNames"); + } + if (_assemblySpecs.ContainsKey(name)) + { + throw new Exception($"Invalid xml file {configFile}, assembly name {name} is duplicated"); + } + assemblySpec.Name = name; + + var types = new List(); + assemblySpec.Types = types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(configFile, childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TypeSpec ParseType(string configFile, XmlElement element) + { + var typeSpec = new TypeSpec(); + + string name = element.GetAttribute("name"); + typeSpec.Name = name; + typeSpec.NameMatcher = new NameMatcher(name); + + var fields = new List(); + typeSpec.Fields = fields; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "field": + { + fields.Add(ParseField(configFile, ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private FieldSpec ParseField(string configFile, XmlElement element) + { + var fieldSpec = new FieldSpec(); + string name = element.GetAttribute("name"); + fieldSpec.Name = name; + fieldSpec.NameMatcher = new NameMatcher(name); + fieldSpec.Rule = _ruleParser(configFile, element); + return fieldSpec; + } + + public R GetFieldRule(FieldDef field) + { + var assemblyName = field.DeclaringType.Module.Assembly.Name; + if (!_assemblySpecs.TryGetValue(assemblyName, out var assSpec)) + { + return null; + } + string declaringTypeName = field.DeclaringType.FullName; + foreach (var typeSpec in assSpec.Types) + { + if (typeSpec.NameMatcher.IsMatch(declaringTypeName)) + { + foreach (var fieldSpec in typeSpec.Fields) + { + if (fieldSpec.NameMatcher.IsMatch(field.Name)) + { + return fieldSpec.Rule; + } + } + } + } + return null; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta new file mode 100644 index 00000000..d8995b9e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Conf/XmlFieldRuleParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1578270b9b81e1e4dba84d562c91090f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs new file mode 100644 index 00000000..3a94a919 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs @@ -0,0 +1,544 @@ +using dnlib.DotNet; +using Obfuz.ObfusPasses; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; +using UnityEngine; + +namespace Obfuz +{ + public class ConfigurablePassPolicy + { + class PassRule + { + public ObfuscationPassType? enablePasses; + public ObfuscationPassType? disablePasses; + public ObfuscationPassType? addPasses; + public ObfuscationPassType? removePasses; + public ObfuscationPassType finalPasses; + + public void InheritParent(PassRule parentRule, ObfuscationPassType globalEnabledPasses) + { + finalPasses = parentRule.finalPasses; + if (enablePasses != null) + { + finalPasses = enablePasses.Value; + } + if (disablePasses != null) + { + finalPasses = ~disablePasses.Value; + } + if (addPasses != null) + { + finalPasses |= addPasses.Value; + } + if (removePasses != null) + { + finalPasses &= ~removePasses.Value; + } + finalPasses &= globalEnabledPasses; + } + } + + class SpecBase + { + public string name; + public NameMatcher nameMatcher; + public PassRule rule; + } + + class MethodSpec : SpecBase + { + } + + class FieldSpec : SpecBase + { + } + + class PropertySpec : SpecBase + { + } + + class EventSpec : SpecBase + { + } + + class TypeSpec : SpecBase + { + public List fields = new List(); + public List methods = new List(); + public List properties = new List(); + public List events = new List(); + } + + class AssemblySpec + { + public string name; + public NameMatcher nameMatcher; + public PassRule rule; + public List types = new List(); + } + + private readonly ObfuscationPassType _enabledPasses; + private readonly HashSet _toObfuscatedAssemblyNames; + private readonly List _assemblySpecs = new List(); + private readonly PassRule _defaultPassRule; + + private string _curLoadingConfig; + + public ConfigurablePassPolicy(IEnumerable toObfuscatedAssemblyNames, ObfuscationPassType enabledPasses, List configFiles) + { + _toObfuscatedAssemblyNames = new HashSet(toObfuscatedAssemblyNames); + _enabledPasses = enabledPasses; + _defaultPassRule = new PassRule { finalPasses = enabledPasses }; + LoadConfigs(configFiles); + InheritParentRules(enabledPasses); + } + + private void LoadConfigs(IEnumerable configFiles) + { + foreach (var configFile in configFiles) + { + LoadConfig(configFile); + } + } + + private void InheritParentRules(ObfuscationPassType enablePasses) + { + var defaultRule = new PassRule + { + enablePasses = enablePasses, + finalPasses = enablePasses, + }; + foreach (AssemblySpec assSpec in _assemblySpecs) + { + assSpec.rule.InheritParent(defaultRule, enablePasses); + foreach (TypeSpec typeSpec in assSpec.types) + { + typeSpec.rule.InheritParent(assSpec.rule, enablePasses); + foreach (FieldSpec fieldSpec in typeSpec.fields) + { + fieldSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (MethodSpec methodSpec in typeSpec.methods) + { + methodSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (PropertySpec propertySpec in typeSpec.properties) + { + propertySpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + foreach (EventSpec eventSpec in typeSpec.events) + { + eventSpec.rule.InheritParent(typeSpec.rule, enablePasses); + } + } + } + } + + public void LoadConfig(string configFile) + { + if (string.IsNullOrEmpty(configFile)) + { + throw new Exception($"Invalid xml file {configFile}, file name is empty"); + } + _curLoadingConfig = configFile; + + Debug.Log($"ConfigurablePassPolicy::LoadConfig {configFile}"); + var doc = new XmlDocument(); + doc.Load(configFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {configFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "assembly": + { + AssemblySpec assSpec = ParseAssembly(ele); + _assemblySpecs.Add(assSpec); + break; + } + default: + { + throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + } + } + + (bool, ObfuscationPassType) ParseObfuscationType(string obfuscationPassTypesStr) + { + bool delta = false; + if (obfuscationPassTypesStr[0] == '+' || obfuscationPassTypesStr[0] == '-') + { + delta = true; + obfuscationPassTypesStr = obfuscationPassTypesStr.Substring(1); + } + ObfuscationPassType passType = ObfuscationPassType.None; + foreach (var passName in obfuscationPassTypesStr.Split('|')) + { + if (Enum.TryParse(passName, out var pass)) + { + passType |= pass; + } + else + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, unknown pass type {passName}"); + } + } + return (delta, passType); + } + + private PassRule ParseRule(XmlElement ele) + { + var r = new PassRule(); + if (ele.HasAttribute("enable")) + { + string enablePassStr = ele.GetAttribute("enable"); + if (string.IsNullOrEmpty(enablePassStr)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, enable attribute is empty"); + } + var (delta, passType) = ParseObfuscationType(enablePassStr); + if (delta) + { + r.addPasses = passType; + } + else + { + r.enablePasses = passType; + } + } + if (ele.HasAttribute("disable")) + { + string disablePassStr = ele.GetAttribute("disable"); + if (string.IsNullOrEmpty(disablePassStr)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, disable attribute is empty"); + } + var (delta, passType) = ParseObfuscationType(disablePassStr); + if (delta) + { + r.removePasses = passType; + } + else + { + r.disablePasses = passType; + } + } + if (r.enablePasses != null && (r.disablePasses != null || r.addPasses != null || r.removePasses != null)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, enable and disable can't be used together"); + } + if (r.disablePasses != null && (r.enablePasses != null || r.addPasses != null || r.removePasses != null)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, disable and enable can't be used together"); + } + return r; + } + + private AssemblySpec ParseAssembly(XmlElement ele) + { + var assemblySpec = new AssemblySpec(); + string name = ele.GetAttribute("name"); + if (!_toObfuscatedAssemblyNames.Contains(name)) + { + throw new Exception($"Invalid xml file {_curLoadingConfig}, assembly name {name} isn't in toObfuscatedAssemblyNames"); + } + assemblySpec.name = name; + assemblySpec.nameMatcher = new NameMatcher(name); + assemblySpec.rule = ParseRule(ele); + + + var types = assemblySpec.types; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "type": + { + types.Add(ParseType(childEle)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + return assemblySpec; + } + + private TypeSpec ParseType(XmlElement element) + { + var typeSpec = new TypeSpec(); + + string name = element.GetAttribute("name"); + typeSpec.name = name; + typeSpec.nameMatcher = new NameMatcher(name); + typeSpec.rule = ParseRule(element); + + List fields = typeSpec.fields; + List methods = typeSpec.methods; + List properties = typeSpec.properties; + List events = typeSpec.events; + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "field": + { + fields.Add(ParseField(ele)); + break; + } + case "method": + { + methods.Add(ParseMethod(ele)); + break; + } + case "property": + { + properties.Add(ParseProperty(ele)); + break; + } + case "event": + { + events.Add(ParseEvent(ele)); + break; + } + default: + { + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + } + return typeSpec; + } + + private void ParseSpecObject(XmlElement element, SpecBase obj) + { + string name = element.GetAttribute("name"); + obj.name = name; + obj.nameMatcher = new NameMatcher(name); + obj.rule = ParseRule(element); + } + + private FieldSpec ParseField(XmlElement element) + { + var fieldSpec = new FieldSpec(); + ParseSpecObject(element, fieldSpec); + return fieldSpec; + } + + private MethodSpec ParseMethod(XmlElement element) + { + var methodSpec = new MethodSpec(); + ParseSpecObject(element, methodSpec); + return methodSpec; + } + + private PropertySpec ParseProperty(XmlElement element) + { + var propertySpec = new PropertySpec(); + ParseSpecObject(element, propertySpec); + return propertySpec; + } + + private EventSpec ParseEvent(XmlElement element) + { + var eventSpec = new EventSpec(); + ParseSpecObject(element, eventSpec); + return eventSpec; + } + + private readonly Dictionary _modulePassRuleCaches = new Dictionary(); + private readonly Dictionary _typePassRuleCaches = new Dictionary(); + private readonly Dictionary _methodPassRuleCaches = new Dictionary(); + private readonly Dictionary _fieldPassRuleCaches = new Dictionary(); + private readonly Dictionary _propertyPassRuleCaches = new Dictionary(); + private readonly Dictionary _eventPassRuleCaches = new Dictionary(); + + + private (AssemblySpec, PassRule) GetAssemblySpec(ModuleDef module) + { + if (!_modulePassRuleCaches.TryGetValue(module, out var result)) + { + result = (null, _defaultPassRule); + string assName = module.Assembly.Name; + foreach (var ass in _assemblySpecs) + { + if (ass.nameMatcher.IsMatch(assName)) + { + result = (ass, ass.rule); + break; + } + } + _modulePassRuleCaches.Add(module, result); + } + return result; + } + + private (TypeSpec, PassRule) GetTypeSpec(TypeDef type) + { + if (!_typePassRuleCaches.TryGetValue(type, out var result)) + { + var assResult = GetAssemblySpec(type.Module); + result = (null, assResult.Item2); + if (assResult.Item1 != null) + { + string typeName = type.FullName; + foreach (var typeSpec in assResult.Item1.types) + { + if (typeSpec.nameMatcher.IsMatch(typeName)) + { + result = (typeSpec, typeSpec.rule); + break; + } + } + } + _typePassRuleCaches.Add(type, result); + } + return result; + } + + private (MethodSpec, PassRule) GetMethodSpec(MethodDef method) + { + if (!_methodPassRuleCaches.TryGetValue(method, out var result)) + { + var typeResult = GetTypeSpec(method.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string methodName = method.Name; + foreach (var methodSpec in typeResult.Item1.methods) + { + if (methodSpec.nameMatcher.IsMatch(methodName)) + { + result = (methodSpec, methodSpec.rule); + break; + } + } + } + _methodPassRuleCaches.Add(method, result); + } + return result; + } + + private (FieldSpec, PassRule) GetFieldSpec(FieldDef field) + { + if (!_fieldPassRuleCaches.TryGetValue(field, out var result)) + { + var typeResult = GetTypeSpec(field.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string fieldName = field.Name; + foreach (var fieldSpec in typeResult.Item1.fields) + { + if (fieldSpec.nameMatcher.IsMatch(fieldName)) + { + result = (fieldSpec, fieldSpec.rule); + break; + } + } + } + _fieldPassRuleCaches.Add(field, result); + } + return result; + } + + private (PropertySpec, PassRule) GetPropertySpec(PropertyDef property) + { + if (!_propertyPassRuleCaches.TryGetValue(property, out var result)) + { + var typeResult = GetTypeSpec(property.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string propertyName = property.Name; + foreach (var propertySpec in typeResult.Item1.properties) + { + if (propertySpec.nameMatcher.IsMatch(propertyName)) + { + result = (propertySpec, propertySpec.rule); + break; + } + } + } + _propertyPassRuleCaches.Add(property, result); + } + return result; + } + + private (EventSpec, PassRule) GetEventSpec(EventDef eventDef) + { + if (!_eventPassRuleCaches.TryGetValue(eventDef, out var result)) + { + var typeResult = GetTypeSpec(eventDef.DeclaringType); + result = (null, typeResult.Item2); + if (typeResult.Item1 != null) + { + string eventName = eventDef.Name; + foreach (var eventSpec in typeResult.Item1.events) + { + if (eventSpec.nameMatcher.IsMatch(eventName)) + { + result = (eventSpec, eventSpec.rule); + break; + } + } + } + _eventPassRuleCaches.Add(eventDef, result); + } + return result; + } + + + public ObfuscationPassType GetAssemblyObfuscationPasses(ModuleDef module) + { + return GetAssemblySpec(module).Item2.finalPasses; + } + + public ObfuscationPassType GetTypeObfuscationPasses(TypeDef type) + { + return GetTypeSpec(type).Item2.finalPasses; + } + + public ObfuscationPassType GetMethodObfuscationPasses(MethodDef method) + { + return GetMethodSpec(method).Item2.finalPasses; + } + + public ObfuscationPassType GetFieldObfuscationPasses(FieldDef field) + { + return GetFieldSpec(field).Item2.finalPasses; + } + + public ObfuscationPassType GetPropertyObfuscationPasses(PropertyDef property) + { + return GetPropertySpec(property).Item2.finalPasses; + } + + public ObfuscationPassType GetEventObfuscationPasses(EventDef eventDef) + { + return GetEventSpec(eventDef).Item2.finalPasses; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta new file mode 100644 index 00000000..9b85183e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConfigurablePassPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41044699810a34f4780e14de084bf7d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs new file mode 100644 index 00000000..90c3a3bd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace Obfuz.Editor +{ + public static class ConstValues + { + public const string ObfuzInternalSymbolNamePrefix = "$Obfuz$"; + + public const string ObfuzRuntimeAssemblyName = "Obfuz.Runtime"; + + public const string ObfuzIgnoreAttributeFullName = "Obfuz.ObfuzIgnoreAttribute"; + + public const string ObfuzScopeFullName = "Obfuz.ObfuzScope"; + + public const string EncryptFieldAttributeFullName = "Obfuz.EncryptFieldAttribute"; + public const string GeneratedEncryptionVirtualMachineFullName = "Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine"; + + public const string EmbeddedAttributeFullName = "Microsoft.CodeAnalysis.EmbeddedAttribute"; + + public const string MonoPInvokeCallbackAttributeName = "MonoPInvokeCallbackAttribute"; + + public const string ZluaLuaInvokeAttributeFullName = "Zlua.LuaInvokeAttribute"; + public const string ZluaLuaCallbackAttributeFullName = "Zlua.LuaCallbackAttribute"; + public const string ZluaLuaMarshalAsAttributeFullName = "Zlua.LuaMarshalAsAttribute"; + + public const string BurstCompileFullName = "Unity.Burst.BurstCompileAttribute"; + public const string DOTSCompilerGeneratedAttributeFullName = "Unity.Jobs.DOTSCompilerGeneratedAttribute"; + + public const string RuntimeInitializedOnLoadMethodAttributeFullName = "UnityEngine.RuntimeInitializeOnLoadMethodAttribute"; + public const string BlackboardEnumAttributeFullName = "Unity.Behavior.BlackboardEnumAttribute"; + + public const string CompilerGeneratedAttributeFullName = "System.Runtime.CompilerServices.CompilerGeneratedAttribute"; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta new file mode 100644 index 00000000..33f41601 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ConstValues.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aee7817ed523a5e4ea42104013e8a775 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data.meta new file mode 100644 index 00000000..d162ae8e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3319ebe75a42f3d4d996846ca09ed099 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs new file mode 100644 index 00000000..90f889d1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs @@ -0,0 +1,269 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Editor; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.Assertions; + +namespace Obfuz.Data +{ + public class ConstFieldAllocator : GroupByModuleEntityBase + { + private RandomCreator _randomCreator; + private IEncryptor _encryptor; + + private TypeDef _holderTypeDef; + + class ConstFieldInfo + { + public FieldDef field; + public object value; + } + + class AnyComparer : IEqualityComparer + { + public new bool Equals(object x, object y) + { + if (x is byte[] xBytes && y is byte[] yBytes) + { + return StructuralComparisons.StructuralEqualityComparer.Equals(xBytes, yBytes); + } + return x.Equals(y); + } + + public static int ComputeHashCode(object obj) + { + return HashUtil.ComputePrimitiveOrStringOrBytesHashCode(obj); + } + + public int GetHashCode(object obj) + { + return ComputeHashCode(obj); + } + } + + private readonly Dictionary _allocatedFields = new Dictionary(new AnyComparer()); + private readonly Dictionary _field2Fields = new Dictionary(); + + private readonly List _holderTypeDefs = new List(); + private bool _done; + + + public ConstFieldAllocator() + { + } + + public override void Init() + { + _randomCreator = EncryptionScope.localRandomCreator; + _encryptor = EncryptionScope.encryptor; + } + + const int maxFieldCount = 1000; + + + private TypeSig GetTypeSigOfValue(object value) + { + ModuleDef mod = Module; + if (value is int) + return mod.CorLibTypes.Int32; + if (value is long) + return mod.CorLibTypes.Int64; + if (value is float) + return mod.CorLibTypes.Single; + if (value is double) + return mod.CorLibTypes.Double; + if (value is string) + return mod.CorLibTypes.String; + if (value is byte[]) + return new SZArraySig(mod.CorLibTypes.Byte); + throw new NotSupportedException($"Unsupported type: {value.GetType()}"); + } + + private ConstFieldInfo CreateConstFieldInfo(object value) + { + ModuleDef mod = Module; + if (_holderTypeDef == null || _holderTypeDef.Fields.Count >= maxFieldCount) + { + using (var scope = new DisableTypeDefFindCacheScope(mod)) + { + ITypeDefOrRef objectTypeRef = mod.Import(typeof(object)); + _holderTypeDef = new TypeDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}ConstFieldHolder${_holderTypeDefs.Count}", objectTypeRef); + mod.Types.Add(_holderTypeDef); + _holderTypeDefs.Add(_holderTypeDef); + } + } + + var field = new FieldDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}RVA_Value{_holderTypeDef.Fields.Count}", new FieldSig(GetTypeSigOfValue(value)), FieldAttributes.Static | FieldAttributes.Public); + field.DeclaringType = _holderTypeDef; + return new ConstFieldInfo + { + field = field, + value = value, + }; + } + + private FieldDef AllocateAny(object value) + { + if (_done) + { + throw new Exception("can't Allocate after done"); + } + if (!_allocatedFields.TryGetValue(value, out var field)) + { + field = CreateConstFieldInfo(value); + _allocatedFields.Add(value, field); + _field2Fields.Add(field.field, field); + } + return field.field; + } + + public FieldDef Allocate(int value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(long value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(float value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(double value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(string value) + { + return AllocateAny(value); + } + + public FieldDef Allocate(byte[] value) + { + return AllocateAny(value); + } + + + private void CreateCCtorOfRvaTypeDef(TypeDef type) + { + ModuleDef mod = Module; + var cctor = new MethodDefUser(".cctor", + MethodSig.CreateStatic(mod.CorLibTypes.Void), + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private); + cctor.DeclaringType = type; + var body = new CilBody(); + cctor.Body = body; + var ins = body.Instructions; + + + DefaultMetadataImporter importer = this.GetDefaultModuleMetadataImporter(); + RvaDataAllocator rvaDataAllocator = GetEntity(); + // TODO. obfuscate init codes + foreach (var field in type.Fields) + { + ConstFieldInfo constInfo = _field2Fields[field]; + IRandom localRandom = _randomCreator(HashUtil.ComputePrimitiveOrStringOrBytesHashCode(constInfo.value)); + int ops = EncryptionUtil.GenerateEncryptionOpCodes(localRandom, _encryptor, 4); + int salt = localRandom.NextInt(); + switch (constInfo.value) + { + case int i: + { + int encryptedValue = _encryptor.Encrypt(i, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt)); + break; + } + case long l: + { + long encryptedValue = _encryptor.Encrypt(l, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaLong)); + break; + } + case float f: + { + float encryptedValue = _encryptor.Encrypt(f, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaFloat)); + break; + } + case double d: + { + double encryptedValue = _encryptor.Encrypt(d, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaDouble)); + break; + } + case string s: + { + byte[] encryptedValue = _encryptor.Encrypt(s, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + Assert.AreEqual(encryptedValue.Length, rvaData.size); + ins.Add(Instruction.CreateLdcI4(encryptedValue.Length)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaString)); + break; + } + case byte[] bs: + { + byte[] encryptedValue = _encryptor.Encrypt(bs, 0, bs.Length, ops, salt); + Assert.AreEqual(encryptedValue.Length, bs.Length); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(bs.Length)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaBytes)); + break; + } + default: throw new NotSupportedException($"Unsupported type: {constInfo.value.GetType()}"); + } + ins.Add(Instruction.Create(OpCodes.Stsfld, field)); + } + ins.Add(Instruction.Create(OpCodes.Ret)); + } + + public override void Done() + { + if (_done) + { + throw new Exception("Already done"); + } + _done = true; + foreach (var typeDef in _holderTypeDefs) + { + CreateCCtorOfRvaTypeDef(typeDef); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta new file mode 100644 index 00000000..8e64e42e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/ConstFieldAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e75f5cdfd47370d4ea6c4dee7e55a881 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs new file mode 100644 index 00000000..00736502 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs @@ -0,0 +1,324 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine.Assertions; + +namespace Obfuz.Data +{ + public struct RvaData + { + public readonly FieldDef field; + public readonly int offset; + public readonly int size; + + public RvaData(FieldDef field, int offset, int size) + { + this.field = field; + this.offset = offset; + this.size = size; + } + } + + public class RvaDataAllocator : GroupByModuleEntityBase + { + const int maxRvaDataSize = 2 * 1024; + + // in HybridCLR version below 8.3.0, the max total static field size of a type is 16KB, so we limit the total size of RVA data to 16KB + const int maxTotalRvaDataFieldSizeInHybridCLR = 16 * 1024; + + private IRandom _random; + + class RvaField + { + public FieldDef holderDataField; + public FieldDef runtimeValueField; + public int encryptionOps; + public uint size; + public List bytes; + public int salt; + + public void FillPaddingToSize(int newSize) + { + for (int i = bytes.Count; i < newSize; i++) + { + bytes.Add(0xAB); + } + } + + public void FillPaddingToEnd() + { + // fill with random value + for (int i = bytes.Count; i < size; i++) + { + bytes.Add(0xAB); + } + } + } + + private class RvaTypeDefInfo + { + public readonly TypeDef typeDef; + public readonly int index; + public readonly List rvaFields = new List(); + + public RvaTypeDefInfo(TypeDef typeDef, int index) + { + this.typeDef = typeDef; + this.index = index; + } + } + + private RvaField _currentField; + + private RvaTypeDefInfo _currentRvaType; + private readonly List _rvaTypeDefs = new List(); + + private readonly Dictionary _dataHolderTypeBySizes = new Dictionary(); + private bool _done; + + public RvaDataAllocator() + { + } + + public override void Init() + { + _random = EncryptionScope.localRandomCreator(HashUtil.ComputeHash(Module.Name)); + } + + private (FieldDef, FieldDef) CreateDataHolderRvaField(TypeDef dataHolderType) + { + if (_currentRvaType == null || _currentRvaType.rvaFields.Count >= maxTotalRvaDataFieldSizeInHybridCLR / maxRvaDataSize - 1) + { + using (var scope = new DisableTypeDefFindCacheScope(Module)) + { + var rvaTypeDef = new TypeDefUser($"$Obfuz$RVA${_rvaTypeDefs.Count}", Module.CorLibTypes.Object.ToTypeDefOrRef()); + Module.Types.Add(rvaTypeDef); + _currentRvaType = new RvaTypeDefInfo(rvaTypeDef, _rvaTypeDefs.Count); + _rvaTypeDefs.Add(_currentRvaType); + } + } + + var holderField = new FieldDefUser($"$RVA_Data{_currentRvaType.rvaFields.Count}", new FieldSig(dataHolderType.ToTypeSig()), FieldAttributes.InitOnly | FieldAttributes.Static | FieldAttributes.HasFieldRVA); + holderField.DeclaringType = _currentRvaType.typeDef; + + var runtimeValueField = new FieldDefUser($"$RVA_Value{_currentRvaType.rvaFields.Count}", new FieldSig(new SZArraySig(Module.CorLibTypes.Byte)), FieldAttributes.Static | FieldAttributes.Public); + runtimeValueField.DeclaringType = _currentRvaType.typeDef; + return (holderField, runtimeValueField); + } + + private TypeDef GetDataHolderType(int size) + { + size = (size + 15) & ~15; // align to 16 bytes + if (_dataHolderTypeBySizes.TryGetValue(size, out var type)) + return type; + + using (var scope = new DisableTypeDefFindCacheScope(Module)) + { + var dataHolderType = new TypeDefUser($"$ObfuzRVA$DataHolder{size}", Module.Import(typeof(ValueType))); + dataHolderType.Attributes = TypeAttributes.Public | TypeAttributes.Sealed; + dataHolderType.Layout = TypeAttributes.ExplicitLayout; + dataHolderType.PackingSize = 1; + dataHolderType.ClassSize = (uint)size; + _dataHolderTypeBySizes.Add(size, dataHolderType); + Module.Types.Add(dataHolderType); + return dataHolderType; + } + } + + private static int AlignTo(int size, int alignment) + { + return (size + alignment - 1) & ~(alignment - 1); + } + + private RvaField CreateRvaField(int size) + { + TypeDef dataHolderType = GetDataHolderType(size); + var (holderDataField, runtimeValueField) = CreateDataHolderRvaField(dataHolderType); + var newRvaField = new RvaField + { + holderDataField = holderDataField, + runtimeValueField = runtimeValueField, + size = dataHolderType.ClassSize, + bytes = new List((int)dataHolderType.ClassSize), + encryptionOps = _random.NextInt(), + salt = _random.NextInt(), + }; + _currentRvaType.rvaFields.Add(newRvaField); + return newRvaField; + } + + private RvaField GetRvaField(int preservedSize, int alignment) + { + if (_done) + { + throw new Exception("can't GetRvaField after done"); + } + Assert.IsTrue(preservedSize % alignment == 0); + // for big size, create a new field + if (preservedSize >= maxRvaDataSize) + { + return CreateRvaField(preservedSize); + } + + if (_currentField != null) + { + int offset = AlignTo(_currentField.bytes.Count, alignment); + + int expectedSize = offset + preservedSize; + if (expectedSize <= _currentField.size) + { + _currentField.FillPaddingToSize(offset); + return _currentField; + } + + _currentField.FillPaddingToEnd(); + } + _currentField = CreateRvaField(maxRvaDataSize); + return _currentField; + } + + public RvaData Allocate(int value) + { + RvaField field = GetRvaField(4, 4); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 4 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 4); + } + + public RvaData Allocate(long value) + { + RvaField field = GetRvaField(8, 8); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 8 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 8); + } + + public RvaData Allocate(float value) + { + RvaField field = GetRvaField(4, 4); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 4 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 4); + } + + public RvaData Allocate(double value) + { + RvaField field = GetRvaField(8, 8); + int offset = field.bytes.Count; + Assert.IsTrue(offset % 8 == 0); + field.bytes.AddRange(BitConverter.GetBytes(value)); + return new RvaData(field.runtimeValueField, offset, 8); + } + + public RvaData Allocate(string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + return Allocate(bytes); + } + + public RvaData Allocate(byte[] value) + { + RvaField field = GetRvaField(value.Length, 1); + int offset = field.bytes.Count; + field.bytes.AddRange(value); + return new RvaData(field.runtimeValueField, offset, value.Length); + } + + + private void AddVerifyCodes(IList insts, DefaultMetadataImporter importer) + { + int verifyIntValue = 0x12345678; + EncryptionScopeInfo encryptionScope = this.EncryptionScope; + IRandom verifyRandom = encryptionScope.localRandomCreator(verifyIntValue); + int verifyOps = EncryptionUtil.GenerateEncryptionOpCodes(verifyRandom, encryptionScope.encryptor, 4); + int verifySalt = verifyRandom.NextInt(); + int encryptedVerifyIntValue = encryptionScope.encryptor.Encrypt(verifyIntValue, verifyOps, verifySalt); + + insts.Add(Instruction.Create(OpCodes.Ldc_I4, verifyIntValue)); + insts.Add(Instruction.CreateLdcI4(encryptedVerifyIntValue)); + insts.Add(Instruction.CreateLdcI4(verifyOps)); + insts.Add(Instruction.CreateLdcI4(verifySalt)); + insts.Add(Instruction.Create(OpCodes.Call, importer.DecryptInt)); + insts.Add(Instruction.Create(OpCodes.Call, importer.VerifySecretKey)); + + } + + private void CreateCCtorOfRvaTypeDef() + { + foreach (RvaTypeDefInfo rvaTypeDef in _rvaTypeDefs) + { + ModuleDef mod = rvaTypeDef.typeDef.Module; + var cctorMethod = new MethodDefUser(".cctor", + MethodSig.CreateStatic(Module.CorLibTypes.Void), + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private); + cctorMethod.DeclaringType = rvaTypeDef.typeDef; + //_rvaTypeDef.Methods.Add(cctor); + var body = new CilBody(); + cctorMethod.Body = body; + var ins = body.Instructions; + + DefaultMetadataImporter importer = this.GetDefaultModuleMetadataImporter(); + AddVerifyCodes(ins, importer); + foreach (var field in rvaTypeDef.rvaFields) + { + // ldc + // newarr + // dup + // stsfld + // ldtoken + // RuntimeHelpers.InitializeArray(array, fieldHandle); + ins.Add(Instruction.Create(OpCodes.Ldc_I4, (int)field.size)); + ins.Add(Instruction.Create(OpCodes.Newarr, field.runtimeValueField.FieldType.Next.ToTypeDefOrRef())); + ins.Add(Instruction.Create(OpCodes.Dup)); + ins.Add(Instruction.Create(OpCodes.Dup)); + ins.Add(Instruction.Create(OpCodes.Stsfld, field.runtimeValueField)); + ins.Add(Instruction.Create(OpCodes.Ldtoken, field.holderDataField)); + ins.Add(Instruction.Create(OpCodes.Call, importer.InitializedArray)); + + // EncryptionService.DecryptBlock(array, field.encryptionOps, field.salt); + ins.Add(Instruction.CreateLdcI4(field.encryptionOps)); + ins.Add(Instruction.Create(OpCodes.Ldc_I4, field.salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptBlock)); + + } + ins.Add(Instruction.Create(OpCodes.Ret)); + } + } + + private void SetFieldsRVA() + { + foreach (var field in _rvaTypeDefs.SelectMany(t => t.rvaFields)) + { + Assert.IsTrue(field.bytes.Count <= field.size); + if (field.bytes.Count < field.size) + { + field.FillPaddingToEnd(); + } + byte[] data = field.bytes.ToArray(); + EncryptionScope.encryptor.EncryptBlock(data, field.encryptionOps, field.salt); + field.holderDataField.InitialValue = data; + } + } + + public override void Done() + { + if (_done) + { + throw new Exception("can't call Done twice"); + } + _done = true; + SetFieldsRVA(); + CreateCCtorOfRvaTypeDef(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta new file mode 100644 index 00000000..c1f94e80 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Data/RvaDataAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c00ca514f46605645bf40b0135e7e504 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit.meta new file mode 100644 index 00000000..d13f8ab9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a513a192808ba5f47b1ef8a3ecf02533 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs new file mode 100644 index 00000000..bab76322 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs @@ -0,0 +1,310 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.Emit +{ + public class BasicBlock + { + public readonly List instructions = new List(); + + public readonly List inBlocks = new List(); + + public readonly List outBlocks = new List(); + + public bool inLoop; + + public void AddTargetBasicBlock(BasicBlock target) + { + if (!outBlocks.Contains(target)) + { + outBlocks.Add(target); + } + if (!target.inBlocks.Contains(this)) + { + target.inBlocks.Add(this); + } + } + } + + public class BasicBlockCollection + { + private readonly MethodDef _method; + + private readonly List _blocks = new List(); + private readonly Dictionary _inst2BlockMap = new Dictionary(); + + public IList Blocks => _blocks; + + public BasicBlockCollection(MethodDef method, bool computeInLoop) + { + _method = method; + HashSet splitPoints = BuildSplitPoint(method); + BuildBasicBlocks(method, splitPoints); + BuildInOutGraph(method); + if (computeInLoop) + { + ComputeBlocksInLoop(); + } + } + + public void ComputeBlocksInLoop() + { + var loopBlocks = FindLoopBlocks(_blocks); + foreach (var block in loopBlocks) + { + block.inLoop = true; + } + } + + public BasicBlock GetBasicBlockByInstruction(Instruction inst) + { + return _inst2BlockMap[inst]; + } + + private HashSet BuildSplitPoint(MethodDef method) + { + var insts = method.Body.Instructions; + var splitPoints = new HashSet(); + foreach (ExceptionHandler eh in method.Body.ExceptionHandlers) + { + if (eh.TryStart != null) + { + splitPoints.Add(eh.TryStart); + } + if (eh.TryEnd != null) + { + splitPoints.Add(eh.TryEnd); + } + if (eh.HandlerStart != null) + { + splitPoints.Add(eh.HandlerStart); + } + if (eh.HandlerEnd != null) + { + splitPoints.Add(eh.HandlerEnd); + } + if (eh.FilterStart != null) + { + splitPoints.Add(eh.FilterStart); + } + } + + for (int i = 0, n = insts.Count; i < n; i++) + { + Instruction curInst = insts[i]; + Instruction nextInst = i + 1 < n ? insts[i + 1] : null; + switch (curInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + splitPoints.Add((Instruction)curInst.Operand); + break; + } + case FlowControl.Cond_Branch: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + if (curInst.Operand is Instruction targetInst) + { + splitPoints.Add(targetInst); + } + else if (curInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + splitPoints.Add(target); + } + } + break; + } + case FlowControl.Return: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + case FlowControl.Throw: + { + if (nextInst != null) + { + splitPoints.Add(nextInst); + } + break; + } + } + } + return splitPoints; + } + + + private void BuildBasicBlocks(MethodDef method, HashSet splitPoints) + { + var insts = method.Body.Instructions; + + + BasicBlock curBlock = new BasicBlock(); + foreach (Instruction inst in insts) + { + if (splitPoints.Contains(inst) && curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + curBlock = new BasicBlock(); + } + curBlock.instructions.Add(inst); + _inst2BlockMap.Add(inst, curBlock); + } + if (curBlock.instructions.Count > 0) + { + _blocks.Add(curBlock); + } + } + + private void BuildInOutGraph(MethodDef method) + { + var insts = method.Body.Instructions; + for (int i = 0, n = _blocks.Count; i < n; i++) + { + BasicBlock curBlock = _blocks[i]; + BasicBlock nextBlock = i + 1 < n ? _blocks[i + 1] : null; + Instruction lastInst = curBlock.instructions.Last(); + switch (lastInst.OpCode.FlowControl) + { + case FlowControl.Branch: + { + Instruction targetInst = (Instruction)lastInst.Operand; + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + break; + } + case FlowControl.Cond_Branch: + { + if (lastInst.Operand is Instruction targetInst) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(targetInst); + curBlock.AddTargetBasicBlock(targetBlock); + } + else if (lastInst.Operand is Instruction[] targetInsts) + { + foreach (var target in targetInsts) + { + BasicBlock targetBlock = GetBasicBlockByInstruction(target); + curBlock.AddTargetBasicBlock(targetBlock); + } + } + else + { + throw new Exception("Invalid operand type for conditional branch"); + } + if (nextBlock != null) + { + curBlock.AddTargetBasicBlock(nextBlock); + } + break; + } + case FlowControl.Call: + case FlowControl.Next: + { + if (nextBlock != null) + { + curBlock.AddTargetBasicBlock(nextBlock); + } + break; + } + case FlowControl.Return: + case FlowControl.Throw: + { + break; + } + default: throw new NotSupportedException($"Unsupported flow control: {lastInst.OpCode.FlowControl} in method {method.FullName}"); + } + } + } + + private static HashSet FindLoopBlocks(List allBlocks) + { + // Tarjan算法找强连通分量 + var sccList = FindStronglyConnectedComponents(allBlocks); + + // 筛选有效循环 + var loopBlocks = new HashSet(); + foreach (var scc in sccList) + { + // 有效循环需满足以下条件之一: + // 1. 分量包含多个块 + // 2. 单个块有自环(跳转自己) + if (scc.Count > 1 || + (scc.Count == 1 && scc[0].outBlocks.Contains(scc[0]))) + { + foreach (var block in scc) + { + loopBlocks.Add(block); + } + } + } + return loopBlocks; + } + + private static List> FindStronglyConnectedComponents(List allBlocks) + { + int index = 0; + var stack = new Stack(); + var indexes = new Dictionary(); + var lowLinks = new Dictionary(); + var onStack = new HashSet(); + var sccList = new List>(); + + foreach (var block in allBlocks.Where(b => !indexes.ContainsKey(b))) + { + StrongConnect(block); + } + + return sccList; + + void StrongConnect(BasicBlock v) + { + indexes[v] = index; + lowLinks[v] = index; + index++; + stack.Push(v); + onStack.Add(v); + + foreach (var w in v.outBlocks) + { + if (!indexes.ContainsKey(w)) + { + StrongConnect(w); + lowLinks[v] = System.Math.Min(lowLinks[v], lowLinks[w]); + } + else if (onStack.Contains(w)) + { + lowLinks[v] = System.Math.Min(lowLinks[v], indexes[w]); + } + } + + if (lowLinks[v] == indexes[v]) + { + var scc = new List(); + BasicBlock w; + do + { + w = stack.Pop(); + onStack.Remove(w); + scc.Add(w); + } while (!w.Equals(v)); + sccList.Add(scc); + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta new file mode 100644 index 00000000..dabfa3b4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/BasicBlockCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77c19c023bb7f77489998d994a3be1bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs new file mode 100644 index 00000000..6399b428 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs @@ -0,0 +1,423 @@ +using dnlib.DotNet; +using System; +using System.Reflection; +using UnityEngine.Assertions; + +namespace Obfuz.Emit +{ + public class EncryptionServiceMetadataImporter + { + private readonly ModuleDef _module; + private readonly Type _encryptionServiceType; + + private IMethod _encryptBlock; + private IMethod _decryptBlock; + private IMethod _encryptInt; + private IMethod _decryptInt; + private IMethod _encryptLong; + private IMethod _decryptLong; + private IMethod _encryptFloat; + private IMethod _decryptFloat; + private IMethod _encryptDouble; + private IMethod _decryptDouble; + private IMethod _encryptString; + private IMethod _decryptString; + private IMethod _encryptBytes; + private IMethod _decryptBytes; + + private IMethod _decryptFromRvaInt; + private IMethod _decryptFromRvaLong; + private IMethod _decryptFromRvaFloat; + private IMethod _decryptFromRvaDouble; + private IMethod _decryptFromRvaString; + private IMethod _decryptFromRvaBytes; + + private IMethod _decryptInitializeArray; + + public IMethod EncryptBlock => _encryptBlock; + public IMethod DecryptBlock => _decryptBlock; + + public IMethod EncryptInt => _encryptInt; + public IMethod DecryptInt => _decryptInt; + public IMethod EncryptLong => _encryptLong; + public IMethod DecryptLong => _decryptLong; + public IMethod EncryptFloat => _encryptFloat; + public IMethod DecryptFloat => _decryptFloat; + public IMethod EncryptDouble => _encryptDouble; + public IMethod DecryptDouble => _decryptDouble; + public IMethod EncryptString => _encryptString; + public IMethod DecryptString => _decryptString; + public IMethod EncryptBytes => _encryptBytes; + public IMethod DecryptBytes => _decryptBytes; + + public IMethod DecryptFromRvaInt => _decryptFromRvaInt; + public IMethod DecryptFromRvaLong => _decryptFromRvaLong; + public IMethod DecryptFromRvaFloat => _decryptFromRvaFloat; + public IMethod DecryptFromRvaDouble => _decryptFromRvaDouble; + public IMethod DecryptFromRvaBytes => _decryptFromRvaBytes; + public IMethod DecryptFromRvaString => _decryptFromRvaString; + + public IMethod DecryptInitializeArray => _decryptInitializeArray; + + public EncryptionServiceMetadataImporter(ModuleDef mod, Type encryptionServiceType) + { + _module = mod; + _encryptionServiceType = encryptionServiceType; + _encryptBlock = mod.Import(encryptionServiceType.GetMethod("EncryptBlock", new[] { typeof(byte[]), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptBlock); + _decryptBlock = mod.Import(encryptionServiceType.GetMethod("DecryptBlock", new[] { typeof(byte[]), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptBlock); + _encryptInt = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptInt); + _decryptInt = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptInt); + _encryptLong = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(long), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptLong); + _decryptLong = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(long), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptLong); + _encryptFloat = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(float), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptFloat); + _decryptFloat = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(float), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFloat); + _encryptDouble = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(double), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptDouble); + _decryptDouble = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(double), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptDouble); + _encryptString = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(string), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptString); + _decryptString = mod.Import(encryptionServiceType.GetMethod("DecryptString", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptString); + _encryptBytes = mod.Import(encryptionServiceType.GetMethod("Encrypt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_encryptBytes); + _decryptBytes = mod.Import(encryptionServiceType.GetMethod("Decrypt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptBytes); + + _decryptFromRvaInt = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaInt", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaInt); + _decryptFromRvaLong = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaLong", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaLong); + _decryptFromRvaFloat = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaFloat", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaFloat); + _decryptFromRvaDouble = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaDouble", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaDouble); + _decryptFromRvaBytes = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaBytes", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaBytes); + _decryptFromRvaString = mod.Import(encryptionServiceType.GetMethod("DecryptFromRvaString", new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptFromRvaString); + _decryptInitializeArray = mod.Import(encryptionServiceType.GetMethod("DecryptInitializeArray", new[] { typeof(System.Array), typeof(System.RuntimeFieldHandle), typeof(int), typeof(int), typeof(int) })); + Assert.IsNotNull(_decryptInitializeArray); + } + } + + public class DefaultMetadataImporter : GroupByModuleEntityBase + { + private EncryptionServiceMetadataImporter _defaultEncryptionServiceMetadataImporter; + + + private EncryptionServiceMetadataImporter _staticDefaultEncryptionServiceMetadataImporter; + private EncryptionServiceMetadataImporter _dynamicDefaultEncryptionServiceMetadataImporter; + + public DefaultMetadataImporter() + { + } + + public override void Init() + { + ModuleDef mod = Module; + + var constUtilityType = typeof(ConstUtility); + + _castIntAsFloat = mod.Import(constUtilityType.GetMethod("CastIntAsFloat")); + Assert.IsNotNull(_castIntAsFloat, "CastIntAsFloat not found"); + _castLongAsDouble = mod.Import(constUtilityType.GetMethod("CastLongAsDouble")); + Assert.IsNotNull(_castLongAsDouble, "CastLongAsDouble not found"); + _castFloatAsInt = mod.Import(constUtilityType.GetMethod("CastFloatAsInt")); + Assert.IsNotNull(_castFloatAsInt, "CastFloatAsInt not found"); + _castDoubleAsLong = mod.Import(constUtilityType.GetMethod("CastDoubleAsLong")); + Assert.IsNotNull(_castDoubleAsLong, "CastDoubleAsLong not found"); + + _initializeArray = mod.Import(typeof(System.Runtime.CompilerServices.RuntimeHelpers).GetMethod("InitializeArray", new[] { typeof(Array), typeof(RuntimeFieldHandle) })); + Assert.IsNotNull(_initializeArray); + _verifySecretKey = mod.Import(typeof(AssetUtility).GetMethod("VerifySecretKey", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_verifySecretKey, "VerifySecretKey not found"); + + _obfuscationTypeMapperRegisterType = mod.Import(typeof(ObfuscationTypeMapper).GetMethod("RegisterType", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null)); + Assert.IsNotNull(_obfuscationTypeMapperRegisterType, "ObfuscationTypeMapper.RegisterType not found"); + + var exprUtilityType = typeof(ExprUtility); + _addInt = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_addInt, "ExprUtility.Add(int, int) not found"); + _addLong = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_addLong, "ExprUtility.Add(long, long) not found"); + _addFloat = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_addFloat, "ExprUtility.Add(float, float) not found"); + _addDouble = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_addDouble, "ExprUtility.Add(double, double) not found"); + _addIntPtr = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(IntPtr), typeof(IntPtr) })); + Assert.IsNotNull(_addIntPtr, "ExprUtility.Add(IntPtr, IntPtr) not found"); + _addIntPtrInt = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(IntPtr), typeof(int) })); + Assert.IsNotNull(_addIntPtrInt, "ExprUtility.Add(IntPtr, int) not found"); + + _subtractInt = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_subtractInt, "ExprUtility.Subtract(int, int) not found"); + _subtractLong = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_subtractLong, "ExprUtility.Subtract(long, long) not found"); + _subtractFloat = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_subtractFloat, "ExprUtility.Subtract(float, float) not found"); + _subtractDouble = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_subtractDouble, "ExprUtility.Subtract(double, double) not found"); + _subtractIntPtr = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(IntPtr), typeof(IntPtr) })); + Assert.IsNotNull(_subtractIntPtr, "ExprUtility.Subtract(IntPtr, IntPtr) not found"); + _subtractIntPtrInt = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(IntPtr), typeof(int) })); + Assert.IsNotNull(_subtractIntPtrInt, "ExprUtility.Subtract(IntPtr, int) not found"); + + _multiplyInt = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_multiplyInt, "ExprUtility.Multiply(int, int) not found"); + _multiplyLong = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_multiplyLong, "ExprUtility.Multiply(long, long) not found"); + _multiplyFloat = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_multiplyFloat, "ExprUtility.Multiply(float, float) not found"); + _multiplyDouble = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_multiplyDouble, "ExprUtility.Multiply(double, double) not found"); + _multiplyIntPtr = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(IntPtr), typeof(IntPtr) })); + Assert.IsNotNull(_multiplyIntPtr, "ExprUtility.Multiply(IntPtr, IntPtr) not found"); + _multiplyIntPtrInt = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(IntPtr), typeof(int) })); + Assert.IsNotNull(_multiplyIntPtrInt, "ExprUtility.Multiply(IntPtr, int) not found"); + + _divideInt = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_divideInt, "ExprUtility.Divide(int, int) not found"); + _divideLong = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_divideLong); + _divideFloat = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_divideFloat, "ExprUtility.Divide(float, float) not found"); + _divideDouble = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_divideDouble, "ExprUtility.Divide(double, double) not found"); + _divideUnInt = mod.Import(exprUtilityType.GetMethod("DivideUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_divideUnInt, "ExprUtility.DivideUn(int, int) not found"); + _divideUnLong = mod.Import(exprUtilityType.GetMethod("DivideUn", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_divideUnLong, "ExprUtility.DivideUn(long, long) not found"); + _remInt = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_remInt, "ExprUtility.Rem(int, int) not found"); + _remLong = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_remLong, "ExprUtility.Rem(long, long) not found"); + _remFloat = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_remFloat, "ExprUtility.Rem(float, float) not found"); + _remDouble = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_remDouble, "ExprUtility.Rem(double, double) not found"); + _remUnInt = mod.Import(exprUtilityType.GetMethod("RemUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_remUnInt, "ExprUtility.RemUn(int, int) not found"); + _remUnLong = mod.Import(exprUtilityType.GetMethod("RemUn", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_remUnLong, "ExprUtility.RemUn(long, long) not found"); + _negInt = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(int) })); + Assert.IsNotNull(_negInt, "ExprUtility.Negate(int) not found"); + _negLong = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(long) })); + Assert.IsNotNull(_negLong, "ExprUtility.Negate(long) not found"); + _negFloat = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(float) })); + Assert.IsNotNull(_negFloat, "ExprUtility.Negate(float) not found"); + _negDouble = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(double) })); + Assert.IsNotNull(_negDouble, "ExprUtility.Negate(double) not found"); + + _andInt = mod.Import(exprUtilityType.GetMethod("And", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_andInt, "ExprUtility.And(int, int) not found"); + _andLong = mod.Import(exprUtilityType.GetMethod("And", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_andLong, "ExprUtility.And(long, long) not found"); + _orInt = mod.Import(exprUtilityType.GetMethod("Or", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_orInt, "ExprUtility.Or(int, int) not found"); + _orLong = mod.Import(exprUtilityType.GetMethod("Or", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_orLong, "ExprUtility.Or(long, long) not found"); + _xorInt = mod.Import(exprUtilityType.GetMethod("Xor", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_xorInt, "ExprUtility.Xor(int, int) not found"); + _xorLong = mod.Import(exprUtilityType.GetMethod("Xor", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_xorLong, "ExprUtility.Xor(long, long) not found"); + _notInt = mod.Import(exprUtilityType.GetMethod("Not", new[] { typeof(int) })); + Assert.IsNotNull(_notInt, "ExprUtility.Not(int) not found"); + _notLong = mod.Import(exprUtilityType.GetMethod("Not", new[] { typeof(long) })); + Assert.IsNotNull(_notLong, "ExprUtility.Not(long) not found"); + + _shlInt = mod.Import(exprUtilityType.GetMethod("ShiftLeft", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shlInt, "ExprUtility.ShiftLeft(int, int) not found"); + _shlLong = mod.Import(exprUtilityType.GetMethod("ShiftLeft", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shlLong, "ExprUtility.ShiftLeft(long, int) not found"); + _shrInt = mod.Import(exprUtilityType.GetMethod("ShiftRight", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shrInt, "ExprUtility.ShiftRight(int, int) not found"); + _shrLong = mod.Import(exprUtilityType.GetMethod("ShiftRight", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shrLong, "ExprUtility.ShiftRight(long, int) not found"); + _shrUnInt = mod.Import(exprUtilityType.GetMethod("ShiftRightUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shrUnInt, "ExprUtility.ShiftRightUn(int, int) not found"); + _shrUnLong = mod.Import(exprUtilityType.GetMethod("ShiftRightUn", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shrUnLong, "ExprUtility.ShiftRightUn(long, int) not found"); + + + _staticDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); + _dynamicDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); + if (EncryptionScopeProvider.IsDynamicSecretAssembly(mod)) + { + _defaultEncryptionServiceMetadataImporter = _dynamicDefaultEncryptionServiceMetadataImporter; + } + else + { + _defaultEncryptionServiceMetadataImporter = _staticDefaultEncryptionServiceMetadataImporter; + } + } + + public override void Done() + { + + } + + public EncryptionServiceMetadataImporter GetEncryptionServiceMetadataImporterOfModule(ModuleDef mod) + { + return EncryptionScopeProvider.IsDynamicSecretAssembly(mod) ? _dynamicDefaultEncryptionServiceMetadataImporter : _staticDefaultEncryptionServiceMetadataImporter; + } + + private ModuleDef _module; + private IMethod _castIntAsFloat; + private IMethod _castLongAsDouble; + private IMethod _castFloatAsInt; + private IMethod _castDoubleAsLong; + private IMethod _initializeArray; + private IMethod _verifySecretKey; + + private IMethod _obfuscationTypeMapperRegisterType; + + private IMethod _addInt; + private IMethod _addLong; + private IMethod _addFloat; + private IMethod _addDouble; + private IMethod _addIntPtr; + private IMethod _addIntPtrInt; + private IMethod _subtractInt; + private IMethod _subtractLong; + private IMethod _subtractFloat; + private IMethod _subtractDouble; + private IMethod _subtractIntPtr; + private IMethod _subtractIntPtrInt; + private IMethod _multiplyInt; + private IMethod _multiplyLong; + private IMethod _multiplyFloat; + private IMethod _multiplyDouble; + private IMethod _multiplyIntPtr; + private IMethod _multiplyIntPtrInt; + private IMethod _divideInt; + private IMethod _divideLong; + private IMethod _divideFloat; + private IMethod _divideDouble; + private IMethod _divideUnInt; + private IMethod _divideUnLong; + private IMethod _remInt; + private IMethod _remLong; + private IMethod _remFloat; + private IMethod _remDouble; + private IMethod _remUnInt; + private IMethod _remUnLong; + private IMethod _negInt; + private IMethod _negLong; + private IMethod _negFloat; + private IMethod _negDouble; + + private IMethod _andInt; + private IMethod _andLong; + private IMethod _orInt; + private IMethod _orLong; + private IMethod _xorInt; + private IMethod _xorLong; + private IMethod _notInt; + private IMethod _notLong; + + private IMethod _shlInt; + private IMethod _shlLong; + private IMethod _shrInt; + private IMethod _shrLong; + private IMethod _shrUnInt; + private IMethod _shrUnLong; + + public IMethod CastIntAsFloat => _castIntAsFloat; + public IMethod CastLongAsDouble => _castLongAsDouble; + public IMethod CastFloatAsInt => _castFloatAsInt; + public IMethod CastDoubleAsLong => _castDoubleAsLong; + + public IMethod InitializedArray => _initializeArray; + + public IMethod VerifySecretKey => _verifySecretKey; + + public IMethod ObfuscationTypeMapperRegisterType => _obfuscationTypeMapperRegisterType; + + public IMethod EncryptBlock => _defaultEncryptionServiceMetadataImporter.EncryptBlock; + public IMethod DecryptBlock => _defaultEncryptionServiceMetadataImporter.DecryptBlock; + + public IMethod EncryptInt => _defaultEncryptionServiceMetadataImporter.EncryptInt; + public IMethod DecryptInt => _defaultEncryptionServiceMetadataImporter.DecryptInt; + public IMethod EncryptLong => _defaultEncryptionServiceMetadataImporter.EncryptLong; + public IMethod DecryptLong => _defaultEncryptionServiceMetadataImporter.DecryptLong; + public IMethod EncryptFloat => _defaultEncryptionServiceMetadataImporter.EncryptFloat; + public IMethod DecryptFloat => _defaultEncryptionServiceMetadataImporter.DecryptFloat; + public IMethod EncryptDouble => _defaultEncryptionServiceMetadataImporter.EncryptDouble; + public IMethod DecryptDouble => _defaultEncryptionServiceMetadataImporter.DecryptDouble; + public IMethod EncryptString => _defaultEncryptionServiceMetadataImporter.EncryptString; + public IMethod DecryptString => _defaultEncryptionServiceMetadataImporter.DecryptString; + public IMethod EncryptBytes => _defaultEncryptionServiceMetadataImporter.EncryptBytes; + public IMethod DecryptBytes => _defaultEncryptionServiceMetadataImporter.DecryptBytes; + + public IMethod DecryptFromRvaInt => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaInt; + public IMethod DecryptFromRvaLong => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaLong; + public IMethod DecryptFromRvaFloat => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaFloat; + public IMethod DecryptFromRvaDouble => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaDouble; + public IMethod DecryptFromRvaBytes => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaBytes; + public IMethod DecryptFromRvaString => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaString; + + public IMethod DecryptInitializeArray => _defaultEncryptionServiceMetadataImporter.DecryptInitializeArray; + + public IMethod AddInt => _addInt; + public IMethod AddLong => _addLong; + public IMethod AddFloat => _addFloat; + public IMethod AddDouble => _addDouble; + public IMethod AddIntPtr => _addIntPtr; + public IMethod AddIntPtrInt => _addIntPtrInt; + public IMethod SubtractInt => _subtractInt; + public IMethod SubtractLong => _subtractLong; + public IMethod SubtractFloat => _subtractFloat; + public IMethod SubtractDouble => _subtractDouble; + public IMethod SubtractIntPtr => _subtractIntPtr; + public IMethod SubtractIntPtrInt => _subtractIntPtrInt; + + public IMethod MultiplyInt => _multiplyInt; + public IMethod MultiplyLong => _multiplyLong; + public IMethod MultiplyFloat => _multiplyFloat; + public IMethod MultiplyDouble => _multiplyDouble; + public IMethod MultiplyIntPtr => _multiplyIntPtr; + public IMethod MultiplyIntPtrInt => _multiplyIntPtrInt; + + public IMethod DivideInt => _divideInt; + public IMethod DivideLong => _divideLong; + public IMethod DivideFloat => _divideFloat; + public IMethod DivideDouble => _divideDouble; + public IMethod DivideUnInt => _divideUnInt; + public IMethod DivideUnLong => _divideUnLong; + public IMethod RemInt => _remInt; + public IMethod RemLong => _remLong; + public IMethod RemFloat => _remFloat; + public IMethod RemDouble => _remDouble; + public IMethod RemUnInt => _remUnInt; + public IMethod RemUnLong => _remUnLong; + public IMethod NegInt => _negInt; + public IMethod NegLong => _negLong; + public IMethod NegFloat => _negFloat; + public IMethod NegDouble => _negDouble; + public IMethod AndInt => _andInt; + public IMethod AndLong => _andLong; + public IMethod OrInt => _orInt; + public IMethod OrLong => _orLong; + public IMethod XorInt => _xorInt; + public IMethod XorLong => _xorLong; + public IMethod NotInt => _notInt; + public IMethod NotLong => _notLong; + public IMethod ShlInt => _shlInt; + public IMethod ShlLong => _shlLong; + public IMethod ShrInt => _shrInt; + public IMethod ShrLong => _shrLong; + public IMethod ShrUnInt => _shrUnInt; + public IMethod ShrUnLong => _shrUnLong; + + + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta new file mode 100644 index 00000000..c29c6a94 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/DefaultMetadataImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76438ce96146edd469872feada7857ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs new file mode 100644 index 00000000..7bc98808 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs @@ -0,0 +1,15 @@ +namespace Obfuz.Emit +{ + public static class EntityExtensions + { + public static T GetEntity(this IGroupByModuleEntity entity) where T : IGroupByModuleEntity, new() + { + return entity.Manager.GetEntity(entity.Module); + } + + public static DefaultMetadataImporter GetDefaultModuleMetadataImporter(this IGroupByModuleEntity entity) + { + return entity.GetEntity(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs.meta new file mode 100644 index 00000000..119466e8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EntityExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e9557733f180764692756653eb60f88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs new file mode 100644 index 00000000..a7fd4f3b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs @@ -0,0 +1,967 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using UnityEngine.Assertions; + +namespace Obfuz.Emit +{ + enum EvalDataType + { + None, + Int32, + Int64, + Float, + Double, + I, + Ref, + ValueType, + Token, + Unknown, + } + + struct EvalDataTypeWithSig + { + public readonly EvalDataType type; + public readonly TypeSig typeSig; + public EvalDataTypeWithSig(EvalDataType type, TypeSig typeSig) + { + this.type = type; + this.typeSig = typeSig; + } + public override string ToString() + { + return $"{type} ({typeSig})"; + } + } + + class InstructionParameterInfo + { + public readonly EvalDataType op1; + public readonly EvalDataType op2; + public readonly EvalDataType retType; + public InstructionParameterInfo(EvalDataType op1, EvalDataType op2, EvalDataType retType) + { + this.op1 = op1; + this.op2 = op2; + this.retType = retType; + } + } + + + class EvalStackState + { + public bool visited; + + public readonly List inputStackDatas = new List(); + public readonly List runStackDatas = new List(); + } + + class EvalStackCalculator + { + private readonly MethodDef _method; + private readonly BasicBlockCollection _basicBlocks; + private readonly Dictionary _instructionParameterInfos = new Dictionary(); + private readonly Dictionary _evalStackTopDataTypeAfterInstructions = new Dictionary(); + private readonly Dictionary _blockEvalStackStates; + + public EvalStackCalculator(MethodDef method) + { + _method = method; + _basicBlocks = new BasicBlockCollection(method, false); + _blockEvalStackStates = _basicBlocks.Blocks.ToDictionary(b => b, b => new EvalStackState()); + + SimulateRunAllBlocks(); + } + + public BasicBlockCollection BasicBlockCollection => _basicBlocks; + + public bool TryGetParameterInfo(Instruction inst, out InstructionParameterInfo info) + { + return _instructionParameterInfos.TryGetValue(inst, out info); + } + + public bool TryGetPushResult(Instruction inst, out EvalDataType result) + { + return _evalStackTopDataTypeAfterInstructions.TryGetValue(inst, out result); + } + + public EvalStackState GetEvalStackState(BasicBlock basicBlock) + { + return _blockEvalStackStates[basicBlock]; + } + + private void PushStack(List datas, TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: break; + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + datas.Add(new EvalDataTypeWithSig(EvalDataType.Int32, null)); + break; + case ElementType.I8: + case ElementType.U8: + datas.Add(new EvalDataTypeWithSig(EvalDataType.Int64, null)); + break; + case ElementType.R4: + datas.Add(new EvalDataTypeWithSig(EvalDataType.Float, null)); + break; + case ElementType.R8: + datas.Add(new EvalDataTypeWithSig(EvalDataType.Double, null)); + break; + case ElementType.I: + case ElementType.U: + case ElementType.Ptr: + case ElementType.FnPtr: + case ElementType.ByRef: + datas.Add(new EvalDataTypeWithSig(EvalDataType.I, null)); + break; + case ElementType.String: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.Object: + datas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, type)); + break; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.IsEnum) + { + PushStack(datas, typeDef.GetEnumUnderlyingType()); + } + else + { + PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type)); + } + break; + } + case ElementType.GenericInst: + { + GenericInstSig genericInstSig = (GenericInstSig)type; + TypeDef typeDef = genericInstSig.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (!typeDef.IsValueType) + { + PushStack(datas, new EvalDataTypeWithSig(EvalDataType.Ref, type)); + } + else if (typeDef.IsEnum) + { + PushStack(datas, typeDef.GetEnumUnderlyingType()); + } + else + { + PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type)); + } + break; + } + case ElementType.TypedByRef: + { + // TypedByRef is a special type used in dynamic method invocation and reflection. + // It is treated as a reference type in the evaluation stack. + PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type)); + break; + } + case ElementType.Var: + case ElementType.MVar: + PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type)); + break; + case ElementType.ValueArray: + case ElementType.R: + case ElementType.CModOpt: + case ElementType.CModReqd: + case ElementType.Internal: + case ElementType.Module: + case ElementType.Sentinel: + PushStack(datas, EvalDataType.Unknown); + break; + + default: throw new Exception($"Unsupported type: {type} in method: {_method.FullName}."); + } + } + + private void PushStack(List datas, ITypeDefOrRef type) + { + PushStack(datas, type.ToTypeSig()); + } + + private void PushStack(List datas, EvalDataType type) + { + Assert.IsTrue(type != EvalDataType.ValueType, "Cannot push EvalDataType.Value without type sig onto the stack."); + datas.Add(new EvalDataTypeWithSig(type, null)); + } + + private void PushStack(List datas, EvalDataTypeWithSig type) + { + datas.Add(type); + } + + private void PushStackObject(List datas) + { + datas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, _method.Module.CorLibTypes.Object)); + } + + private EvalDataType CalcBasicBinOpRetType(EvalDataType op1, EvalDataType op2) + { + switch (op1) + { + case EvalDataType.Int32: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.Int32; + case EvalDataType.Int64: return EvalDataType.Int64; + case EvalDataType.I: return EvalDataType.I; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Int64: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.Int64; + case EvalDataType.Int64: + case EvalDataType.I: + return EvalDataType.Int64; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.I: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.I; + case EvalDataType.Int64: return EvalDataType.Int64; + case EvalDataType.I: return EvalDataType.I; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Float: + { + switch (op2) + { + case EvalDataType.Float: return EvalDataType.Float; + case EvalDataType.Double: return EvalDataType.Double; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Double: + { + switch (op2) + { + case EvalDataType.Float: + case EvalDataType.Double: return EvalDataType.Double; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + default: throw new Exception($"Unsupported operand type: {op1} in binary operation."); + } + } + + private void SimulateRunAllBlocks() + { + bool methodHasReturnValue = !MetaUtil.IsVoidType(_method.ReturnType); + + CilBody body = _method.Body; + if (body.HasExceptionHandlers) + { + foreach (ExceptionHandler handler in body.ExceptionHandlers) + { + if (handler.IsFilter) + { + BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.FilterStart); + var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas; + if (inputStackDatas.Count == 0) + { + inputStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, handler.CatchType.ToTypeSig())); + } + } + if (handler.IsCatch || handler.IsFilter) + { + BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.HandlerStart); + var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas; + if (inputStackDatas.Count == 0) + { + inputStackDatas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, handler.CatchType.ToTypeSig())); + } + } + } + } + + var newPushedDatas = new List(); + IList methodTypeGenericArgument = _method.DeclaringType.GenericParameters.Count > 0 + ? (IList)_method.DeclaringType.GenericParameters.Select(p => (TypeSig)new GenericVar(p.Number)).ToList() + : null; + IList methodMethodGenericArgument = _method.GenericParameters.Count > 0 + ? (IList)_method.GenericParameters.Select(p => (TypeSig)new GenericMVar(p.Number)).ToList() + : null; + var gac = new GenericArgumentContext(methodTypeGenericArgument, methodMethodGenericArgument); + var corLibTypes = _method.Module.CorLibTypes; + + var blockWalkStack = new Stack(_basicBlocks.Blocks.Reverse()); + while (blockWalkStack.Count > 0) + { + BasicBlock block = blockWalkStack.Pop(); + EvalStackState state = _blockEvalStackStates[block]; + if (state.visited) + continue; + state.visited = true; + state.runStackDatas.AddRange(state.inputStackDatas); + List stackDatas = state.runStackDatas; + foreach (var inst in block.instructions) + { + int stackSize = stackDatas.Count; + newPushedDatas.Clear(); + switch (inst.OpCode.Code) + { + case Code.Nop: break; + case Code.Break: break; + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg: + case Code.Ldarg_S: + { + PushStack(newPushedDatas, inst.GetParameter(_method.Parameters).Type); + break; + } + case Code.Ldarga: + case Code.Ldarga_S: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc: + case Code.Ldloc_S: + { + PushStack(newPushedDatas, inst.GetLocal(body.Variables).Type); + break; + } + case Code.Ldloca: + case Code.Ldloca_S: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + case Code.Stloc: + case Code.Stloc_S: + { + Assert.IsTrue(stackSize > 0); + break; + } + case Code.Starg: + case Code.Starg_S: + { + Assert.IsTrue(stackSize > 0); + break; + } + case Code.Ldnull: + { + PushStackObject(newPushedDatas); + break; + } + case Code.Ldc_I4_M1: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4: + case Code.Ldc_I4_S: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldc_I8: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldc_R4: + { + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldc_R8: + { + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Dup: + { + Assert.IsTrue(stackSize > 0); + EvalDataTypeWithSig type = stackDatas[stackSize - 1]; + PushStack(newPushedDatas, type); + PushStack(newPushedDatas, type); + break; + } + case Code.Pop: + { + break; + } + case Code.Jmp: + { + break; + } + case Code.Call: + case Code.Callvirt: + { + IMethod calledMethod = (IMethod)inst.Operand; + MethodSig methodSig = MetaUtil.GetInflatedMethodSig(calledMethod, gac); + PushStack(newPushedDatas, methodSig.RetType); + break; + } + case Code.Calli: + { + MethodSig methodSig = (MethodSig)inst.Operand; + PushStack(newPushedDatas, methodSig.RetType); + break; + } + case Code.Ret: + { + break; + } + case Code.Br: + case Code.Br_S: + case Code.Brfalse: + case Code.Brfalse_S: + case Code.Brtrue: + case Code.Brtrue_S: + case Code.Beq: + case Code.Beq_S: + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: + case Code.Bne_Un: + case Code.Bne_Un_S: + { + // Branch instructions do not change the stack. + break; + } + case Code.Ceq: + case Code.Cgt: + case Code.Cgt_Un: + case Code.Clt: + case Code.Clt_Un: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1].type; + EvalDataType op1 = stackDatas[stackSize - 2].type; + EvalDataType ret = EvalDataType.Int32; + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Switch: + { + // Switch instruction does not change the stack. + break; + } + case Code.Ldind_I1: + case Code.Ldind_U1: + case Code.Ldind_I2: + case Code.Ldind_U2: + case Code.Ldind_I4: + case Code.Ldind_U4: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldind_I8: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldind_I: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldind_Ref: + { + Assert.IsTrue(stackSize > 0); + PushStackObject(newPushedDatas); + break; + } + case Code.Ldind_R4: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldind_R8: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Stind_I1: + case Code.Stind_I2: + case Code.Stind_I4: + case Code.Stind_I8: + case Code.Stind_I: + case Code.Stind_R4: + case Code.Stind_R8: + case Code.Stind_Ref: + { + Assert.IsTrue(stackSize >= 2); + break; + } + + case Code.Add: + case Code.Add_Ovf: + case Code.Add_Ovf_Un: + case Code.Sub: + case Code.Sub_Ovf: + case Code.Sub_Ovf_Un: + case Code.Mul: + case Code.Mul_Ovf: + case Code.Mul_Ovf_Un: + case Code.Div: + case Code.Div_Un: + case Code.Rem: + case Code.Rem_Un: + + case Code.And: + case Code.Or: + case Code.Xor: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1].type; + EvalDataType op1 = stackDatas[stackSize - 2].type; + EvalDataType ret = CalcBasicBinOpRetType(op1, op2); + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Shl: + case Code.Shr: + case Code.Shr_Un: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1].type; + EvalDataType op1 = stackDatas[stackSize - 2].type; + if (op1 != EvalDataType.Int32 && op1 != EvalDataType.Int64 && op1 != EvalDataType.I) + throw new Exception($"Unsupported operand type: {op1} in shift operation."); + if (op2 != EvalDataType.Int32 && op2 != EvalDataType.Int64) + throw new Exception($"Unsupported operand type: {op2} for {op1} in shift operation."); + EvalDataType ret = op1; + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Neg: + { + Assert.IsTrue(stackSize > 0); + EvalDataType op = stackDatas[stackSize - 1].type; + EvalDataType ret = op; + switch (op) + { + case EvalDataType.Int32: + case EvalDataType.Int64: + case EvalDataType.I: + case EvalDataType.Float: + case EvalDataType.Double: + break; + default: + throw new Exception($"Unsupported operand type: {op} in unary operation."); + } + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Not: + { + Assert.IsTrue(stackSize > 0); + EvalDataType op = stackDatas[stackSize - 1].type; + EvalDataType ret = op; + if (op != EvalDataType.Int32 && op != EvalDataType.Int64 && op != EvalDataType.I) + throw new Exception($"Unsupported operand type: {op} in unary operation."); + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Conv_I1: + case Code.Conv_U1: + case Code.Conv_I2: + case Code.Conv_U2: + case Code.Conv_I4: + case Code.Conv_U4: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Conv_I8: + case Code.Conv_U8: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Conv_I: + case Code.Conv_U: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Conv_R4: + { + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Conv_R8: + { + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Conv_Ovf_I1: + case Code.Conv_Ovf_I1_Un: + case Code.Conv_Ovf_U1: + case Code.Conv_Ovf_U1_Un: + case Code.Conv_Ovf_I2: + case Code.Conv_Ovf_I2_Un: + case Code.Conv_Ovf_U2: + case Code.Conv_Ovf_U2_Un: + case Code.Conv_Ovf_I4: + case Code.Conv_Ovf_I4_Un: + case Code.Conv_Ovf_U4: + case Code.Conv_Ovf_U4_Un: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Conv_Ovf_I8: + case Code.Conv_Ovf_I8_Un: + case Code.Conv_Ovf_U8: + case Code.Conv_Ovf_U8_Un: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Conv_Ovf_I: + case Code.Conv_Ovf_I_Un: + case Code.Conv_Ovf_U: + case Code.Conv_Ovf_U_Un: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Conv_R_Un: + { + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Cpobj: + case Code.Initobj: + case Code.Stobj: + { + break; + } + case Code.Ldobj: + { + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Ldstr: + { + PushStack(newPushedDatas, new EvalDataTypeWithSig(EvalDataType.Ref, corLibTypes.String)); + break; + } + case Code.Newobj: + { + IMethod ctor = (IMethod)inst.Operand; + PushStack(newPushedDatas, ctor.DeclaringType); + break; + } + case Code.Castclass: + { + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Isinst: + { + Assert.IsTrue(stackSize > 0); + var obj = stackDatas[stackSize - 1]; + Assert.IsTrue(obj.type == EvalDataType.Ref); + PushStack(newPushedDatas, obj); + break; + } + case Code.Unbox: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Unbox_Any: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Box: + { + Assert.IsTrue(stackSize > 0); + PushStackObject(newPushedDatas); + break; + } + case Code.Throw: + { + // Throw instruction does not change the stack. + break; + } + case Code.Rethrow: + { + // Rethrow instruction does not change the stack. + break; + } + case Code.Ldfld: + case Code.Ldsfld: + { + IField field = (IField)inst.Operand; + TypeSig fieldType = MetaUtil.InflateFieldSig(field, gac); + PushStack(newPushedDatas, fieldType); + break; + } + case Code.Ldflda: + case Code.Ldsflda: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Stfld: + case Code.Stsfld: + { + break; + } + case Code.Newarr: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, new SZArraySig(((ITypeDefOrRef)inst.Operand).ToTypeSig())); + break; + } + case Code.Ldlen: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelema: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelem_I1: + case Code.Ldelem_U1: + case Code.Ldelem_I2: + case Code.Ldelem_U2: + case Code.Ldelem_I4: + case Code.Ldelem_U4: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldelem_I8: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldelem_I: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelem_R4: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldelem_R8: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Ldelem_Ref: + { + Assert.IsTrue(stackSize >= 2); + PushStackObject(newPushedDatas); + break; + } + case Code.Ldelem: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Stelem_I1: + case Code.Stelem_I2: + case Code.Stelem_I4: + case Code.Stelem_I8: + case Code.Stelem_I: + case Code.Stelem_R4: + case Code.Stelem_R8: + case Code.Stelem_Ref: + case Code.Stelem: + { + Assert.IsTrue(stackSize >= 3); + break; + } + case Code.Mkrefany: + { + PushStack(newPushedDatas, new EvalDataTypeWithSig(EvalDataType.ValueType, _method.Module.CorLibTypes.TypedReference)); + break; + } + case Code.Refanytype: + { + PushStack(newPushedDatas, EvalDataType.Token); + break; + } + case Code.Refanyval: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldtoken: + { + PushStack(newPushedDatas, EvalDataType.Token); + break; + } + case Code.Endfinally: + case Code.Leave: + case Code.Leave_S: + { + break; + } + case Code.Endfilter: + { + break; + } + case Code.Arglist: + { + break; + } + case Code.Ldftn: + case Code.Ldvirtftn: + { + PushStack(newPushedDatas, EvalDataType.Unknown); + break; + } + case Code.Localloc: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Unaligned: + case Code.Volatile: + case Code.Tailcall: + case Code.No: + case Code.Readonly: + case Code.Constrained: + { + break; + } + case Code.Cpblk: + case Code.Initblk: + { + break; + } + case Code.Sizeof: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + default: throw new Exception($"not supported opcode: {inst} in method: {_method.FullName}."); + } + + inst.CalculateStackUsage(methodHasReturnValue, out var pushed, out var pops); + if (pushed != newPushedDatas.Count) + { + throw new Exception($"Instruction {inst} in method {_method.FullName} pushed {newPushedDatas.Count} items, but expected {pushed} items."); + } + if (pops == -1) + { + stackDatas.Clear(); + } + else + { + if (stackSize < pops) + { + throw new Exception($"Instruction {inst} in method {_method.FullName} pops {pops} items, but only {stackSize} items are available on the stack."); + } + stackDatas.RemoveRange(stackDatas.Count - pops, pops); + stackDatas.AddRange(newPushedDatas); + Assert.AreEqual(stackSize + pushed - pops, stackDatas.Count); + } + if (pushed > 0 && stackDatas.Count > 0) + { + _evalStackTopDataTypeAfterInstructions[inst] = stackDatas.Last().type; + } + } + foreach (BasicBlock outBb in block.outBlocks) + { + EvalStackState outState = _blockEvalStackStates[outBb]; + if (outState.visited) + { + if (stackDatas.Count != outState.inputStackDatas.Count) + { + throw new Exception($"Block {block} in method {_method.FullName} has inconsistent stack data. Expected {outState.inputStackDatas.Count}, but got {stackDatas.Count}."); + } + } + else if (outState.inputStackDatas.Count != stackDatas.Count) + { + if (outState.inputStackDatas.Count > 0) + { + throw new Exception($"Block {outBb} in method {_method.FullName} has inconsistent stack data. Expected {outState.inputStackDatas.Count}, but got {stackDatas.Count}."); + } + outState.inputStackDatas.AddRange(stackDatas); + blockWalkStack.Push(outBb); + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs.meta new file mode 100644 index 00000000..0e3c9de0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/EvalStackCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f25425a3077f6db41873dee4223d0abc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs new file mode 100644 index 00000000..97e8f286 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs @@ -0,0 +1,89 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; + +namespace Obfuz.Emit +{ + public interface IGroupByModuleEntity + { + GroupByModuleEntityManager Manager { get; set; } + + ModuleDef Module { get; set; } + + public EncryptionScopeProvider EncryptionScopeProvider { get; } + + EncryptionScopeInfo EncryptionScope { get; set; } + + void Init(); + + void Done(); + } + + public abstract class GroupByModuleEntityBase : IGroupByModuleEntity + { + public GroupByModuleEntityManager Manager { get; set; } + + public ModuleDef Module { get; set; } + + public EncryptionScopeInfo EncryptionScope { get; set; } + + public EncryptionScopeProvider EncryptionScopeProvider => Manager.EncryptionScopeProvider; + + public T GetEntity() where T : IGroupByModuleEntity, new() + { + return Manager.GetEntity(Module); + } + + public abstract void Init(); + + public abstract void Done(); + } + + public class GroupByModuleEntityManager + { + private readonly Dictionary<(ModuleDef, Type), IGroupByModuleEntity> _moduleEntityManagers = new Dictionary<(ModuleDef, Type), IGroupByModuleEntity>(); + + public EncryptionScopeProvider EncryptionScopeProvider { get; set; } + + public T GetEntity(ModuleDef mod) where T : IGroupByModuleEntity, new() + { + var key = (mod, typeof(T)); + if (_moduleEntityManagers.TryGetValue(key, out var emitManager)) + { + return (T)emitManager; + } + else + { + T newEmitManager = new T(); + newEmitManager.Manager = this; + newEmitManager.Module = mod; + newEmitManager.EncryptionScope = EncryptionScopeProvider.GetScope(mod); + newEmitManager.Init(); + _moduleEntityManagers[key] = newEmitManager; + return newEmitManager; + } + } + + public List GetEntities() where T : IGroupByModuleEntity, new() + { + var managers = new List(); + foreach (var kv in _moduleEntityManagers) + { + if (kv.Key.Item2 == typeof(T)) + { + managers.Add((T)kv.Value); + } + } + return managers; + } + + public void Done() where T : IGroupByModuleEntity, new() + { + var managers = GetEntities(); + foreach (var manager in managers) + { + manager.Done(); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta new file mode 100644 index 00000000..a6b76025 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/GroupByModuleEntityManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bfcb2b5a87851f469d201fc8978c109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs new file mode 100644 index 00000000..3fb0015e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs @@ -0,0 +1,74 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; + +namespace Obfuz.Emit +{ + class ScopeLocalVariables : IDisposable + { + private readonly LocalVariableAllocator _localVariableAllocator; + + private readonly List _allocatedVars = new List(); + + public IReadOnlyList AllocatedLocals => _allocatedVars; + + + public ScopeLocalVariables(LocalVariableAllocator localVariableAllocator) + { + _localVariableAllocator = localVariableAllocator; + } + + public Local AllocateLocal(TypeSig type) + { + var local = _localVariableAllocator.AllocateLocal(type); + _allocatedVars.Add(local); + return local; + } + + public void Dispose() + { + foreach (var local in _allocatedVars) + { + _localVariableAllocator.ReturnLocal(local); + } + } + } + + class LocalVariableAllocator + { + private readonly MethodDef _method; + private readonly List _freeLocals = new List(); + + public LocalVariableAllocator(MethodDef method) + { + _method = method; + } + + public Local AllocateLocal(TypeSig type) + { + foreach (var local in _freeLocals) + { + if (TypeEqualityComparer.Instance.Equals(local.Type, type)) + { + _freeLocals.Remove(local); + return local; + } + } + var newLocal = new Local(type); + // _freeLocals.Add(newLocal); + _method.Body.Variables.Add(newLocal); + return newLocal; + } + + public void ReturnLocal(Local local) + { + _freeLocals.Add(local); + } + + public ScopeLocalVariables CreateScope() + { + return new ScopeLocalVariables(this); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs.meta new file mode 100644 index 00000000..6561078c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Emit/LocalVariableAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 955da34fbde179641a94108ec53405ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM.meta new file mode 100644 index 00000000..e8e65c15 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fec4187cc1b96d5439ff908bcecd988f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs new file mode 100644 index 00000000..19ded458 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs @@ -0,0 +1,25 @@ +namespace Obfuz.EncryptionVM +{ + public class EncryptionInstructionWithOpCode + { + public readonly ushort code; + + public readonly IEncryptionInstruction function; + + public EncryptionInstructionWithOpCode(ushort code, IEncryptionInstruction function) + { + this.code = code; + this.function = function; + } + + public int Encrypt(int value, int[] secretKey, int salt) + { + return function.Encrypt(value, secretKey, salt); + } + + public int Decrypt(int value, int[] secretKey, int salt) + { + return function.Decrypt(value, secretKey, salt); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta new file mode 100644 index 00000000..cd63f212 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/EncryptionInstructionWithOpCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca9bd232ed2583f4bb5f330886a329e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs new file mode 100644 index 00000000..49808c43 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM +{ + public interface IEncryptionInstruction + { + int Encrypt(int value, int[] secretKey, int salt); + + int Decrypt(int value, int[] secretKey, int salt); + + void GenerateEncryptCode(List lines, string indent); + + void GenerateDecryptCode(List lines, string indent); + } + + public abstract class EncryptionInstructionBase : IEncryptionInstruction + { + public abstract int Encrypt(int value, int[] secretKey, int salt); + public abstract int Decrypt(int value, int[] secretKey, int salt); + + public abstract void GenerateEncryptCode(List lines, string indent); + public abstract void GenerateDecryptCode(List lines, string indent); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta new file mode 100644 index 00000000..ab7f3a60 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/IEncryptionInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7b9d087de770a5488a9069ddf697c2f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta new file mode 100644 index 00000000..fa569881 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 981355cf75a9d234883b2a15c446f478 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs new file mode 100644 index 00000000..2681fff0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddInstruction : EncryptionInstructionBase + { + private readonly int _addValue; + private readonly int _opKeyIndex; + + public AddInstruction(int addValue, int opKeyIndex) + { + _addValue = addValue; + _opKeyIndex = opKeyIndex; + } + public override int Encrypt(int value, int[] secretKey, int salt) + { + return ((value + secretKey[_opKeyIndex]) ^ salt) + _addValue; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return ((value - _addValue) ^ salt) - secretKey[_opKeyIndex]; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value + _secretKey[{_opKeyIndex}]) ^ salt) + {_addValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value - {_addValue}) ^ salt) - _secretKey[{_opKeyIndex}];"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta new file mode 100644 index 00000000..d1d20319 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bdbdc5fd983f044a87e7b8ab8647aeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs new file mode 100644 index 00000000..44933213 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddRotateXorInstruction : EncryptionInstructionBase + { + // x = x + p1 + secretKey[index1]; + // x = Rotate(x, p2) + // x = x ^ p3 ^ salt; + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public AddRotateXorInstruction(int addValue, int index1, int rotateBitNum, int xorValue) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value += _addValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + value ^= _xorValue ^ salt; + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value -= _addValue + secretKey[_index1]; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta new file mode 100644 index 00000000..6101fdb1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddRotateXorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cda67c0dd0cadd24ea02c2988e34281a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs new file mode 100644 index 00000000..d2aa6f15 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class AddXorRotateInstruction : EncryptionInstructionBase + { + // x = x + p1 + secretKey[index1]; + // x = x ^ p3 ^ salt; + // x = Rotate(x, p2) + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public AddXorRotateInstruction(int addValue, int index1, int xorValue, int rotateBitNum) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value += _addValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value ^= _xorValue ^ salt; + value -= _addValue + secretKey[_index1]; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint part1 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta new file mode 100644 index 00000000..abde16f0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/AddXorRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d806305e627be06469fb2d2c2cf98816 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs new file mode 100644 index 00000000..2d9dcd14 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class BitRotateInstruction : EncryptionInstructionBase + { + private readonly int _rotateBitNum; + private readonly int _opKeyIndex; + + public BitRotateInstruction(int rotateBitNum, int opKeyIndex) + { + _rotateBitNum = rotateBitNum; + _opKeyIndex = opKeyIndex; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + return ((int)(part1 | part2) ^ secretKey[_opKeyIndex]) + salt; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)((value - salt) ^ secretKey[_opKeyIndex]); + uint part1 = value2 >> _rotateBitNum; + uint part2 = value2 << (32 - _rotateBitNum); + return (int)(part1 | part2); + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = ((int)(part1 | part2) ^ _secretKey[{_opKeyIndex}]) + salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)((value - salt) ^ _secretKey[{_opKeyIndex}]);"); + lines.Add(indent + $"uint part1 = value2 >> {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = value2 << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta new file mode 100644 index 00000000..b4b0b9a5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/BitRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bccff31b9f07fcf4f821cee671f82caf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs new file mode 100644 index 00000000..7abafecd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + + public class EncryptFunction : EncryptionInstructionBase + { + private readonly IEncryptionInstruction[] _instructions; + + public EncryptFunction(IEncryptionInstruction[] instructions) + { + _instructions = instructions; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + foreach (var instruction in _instructions) + { + value = instruction.Encrypt(value, secretKey, salt); + } + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + for (int i = _instructions.Length - 1; i >= 0; i--) + { + value = _instructions[i].Decrypt(value, secretKey, salt); + } + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + throw new NotImplementedException(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta new file mode 100644 index 00000000..0ee2250b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/EncryptFunction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: feafdb30f7b6d5143a89c7659bc16171 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs new file mode 100644 index 00000000..a788a678 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs @@ -0,0 +1,46 @@ +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleInstruction : EncryptionInstructionBase + { + private readonly int _multiValue; + private readonly int _revertMultiValue; + private readonly int _opKeyIndex; + + public MultipleInstruction(int addValue, int opKeyIndex) + { + _multiValue = addValue; + _opKeyIndex = opKeyIndex; + _revertMultiValue = MathUtil.ModInverse32(addValue); + Verify(); + } + + private void Verify() + { + int a = 1122334; + UnityEngine.Assertions.Assert.AreEqual(a, a * _multiValue * _revertMultiValue); + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + return value * _multiValue + secretKey[_opKeyIndex] + salt; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return (value - secretKey[_opKeyIndex] - salt) * _revertMultiValue; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multiValue} + _secretKey[{_opKeyIndex}] + salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = (value - _secretKey[{_opKeyIndex}] - salt) * {_revertMultiValue};"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta new file mode 100644 index 00000000..478ba088 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd5fdfad694e0ae469bf6ca04c913220 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs new file mode 100644 index 00000000..62fc609a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs @@ -0,0 +1,65 @@ +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleRotateXorInstruction : EncryptionInstructionBase + { + // x = x * p1 + secretKey[index1]; + // x = Rotate(x, p2) + // x = x ^ p3 ^ salt; + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public MultipleRotateXorInstruction(int multipleValue, int index1, int rotateBitNum, int xorValue) + { + _multipleValue = multipleValue; + _revertMultipleValue = MathUtil.ModInverse32(multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value = value * _multipleValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + value ^= _xorValue ^ salt; + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value = (value - secretKey[_index1]) * _revertMultipleValue; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta new file mode 100644 index 00000000..7f4e53a7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleRotateXorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3c8b55b35ff1554489fa657a714f485 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs new file mode 100644 index 00000000..a8a69326 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs @@ -0,0 +1,65 @@ +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class MultipleXorRotateInstruction : EncryptionInstructionBase + { + // x = x * p1 + secretKey[index1]; + // x = x ^ p3 ^ salt; + // x = Rotate(x, p2) + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public MultipleXorRotateInstruction(int multipleValue, int index1, int xorValue, int rotateBitNum) + { + _multipleValue = multipleValue; + _revertMultipleValue = MathUtil.ModInverse32(multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value = value * _multipleValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value ^= _xorValue ^ salt; + value = (value - secretKey[_index1]) * _revertMultipleValue; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta new file mode 100644 index 00000000..0e6a6534 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/MultipleXorRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adc3dcde66795744fa4bdc753a2c599f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs new file mode 100644 index 00000000..97cc6e43 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorAddRotateInstruction : EncryptionInstructionBase + { + // x = x ^ p3 ^ salt; + // x = x + p1 + secretKey[index1]; + // x = Rotate(x, p2) + + private readonly int _addValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public XorAddRotateInstruction(int xorValue, int addValue, int index1, int rotateBitNum) + { + _addValue = addValue; + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + value += _addValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value -= _addValue + secretKey[_index1]; + value ^= _xorValue ^ salt; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value += {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value -= {_addValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta new file mode 100644 index 00000000..d1a6d086 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorAddRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad8f4dd724d7ff845b0dd65861054d37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs new file mode 100644 index 00000000..353a4f6f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorInstruction : EncryptionInstructionBase + { + private readonly int _xorValue; + private readonly int _opKeyIndex; + + public XorInstruction(int xorValue, int opKeyIndex) + { + _xorValue = xorValue; + _opKeyIndex = opKeyIndex; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + return ((value ^ secretKey[_opKeyIndex]) + salt) ^ _xorValue; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + return ((value ^ _xorValue) - salt) ^ secretKey[_opKeyIndex]; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value ^ _secretKey[{_opKeyIndex}]) + salt) ^ {_xorValue};"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"value = ((value ^ {_xorValue}) - salt) ^ _secretKey[{_opKeyIndex}];"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta new file mode 100644 index 00000000..e246896d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f16dd868e4473b45bfa9daaf7fabaf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs new file mode 100644 index 00000000..ab823312 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs @@ -0,0 +1,65 @@ +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM.Instructions +{ + public class XorMultipleRotateInstruction : EncryptionInstructionBase + { + // x = x ^ p3 ^ salt; + // x = x * p1 + secretKey[index1]; + // x = Rotate(x, p2) + + private readonly int _multipleValue; + private readonly int _revertMultipleValue; + private readonly int _index1; + private readonly int _rotateBitNum; + private readonly int _xorValue; + + public XorMultipleRotateInstruction(int xorValue, int multipleValue, int index1, int rotateBitNum) + { + _multipleValue = multipleValue; + _revertMultipleValue = MathUtil.ModInverse32(multipleValue); + _index1 = index1; + _rotateBitNum = rotateBitNum; + _xorValue = xorValue; + } + + public override int Encrypt(int value, int[] secretKey, int salt) + { + value ^= _xorValue ^ salt; + value = value * _multipleValue + secretKey[_index1]; + uint part1 = (uint)value << _rotateBitNum; + uint part2 = (uint)value >> (32 - _rotateBitNum); + value = (int)(part1 | part2); + return value; + } + + public override int Decrypt(int value, int[] secretKey, int salt) + { + uint value2 = (uint)value >> _rotateBitNum; + uint part1 = (uint)value << (32 - _rotateBitNum); + value = (int)(value2 | part1); + value = (value - secretKey[_index1]) * _revertMultipleValue; + value ^= _xorValue ^ salt; + return value; + } + + public override void GenerateEncryptCode(List lines, string indent) + { + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + lines.Add(indent + $"value = value * {_multipleValue} + _secretKey[{_index1}];"); + lines.Add(indent + $"uint part1 = (uint)value << {_rotateBitNum};"); + lines.Add(indent + $"uint part2 = (uint)value >> (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(part1 | part2);"); + } + + public override void GenerateDecryptCode(List lines, string indent) + { + lines.Add(indent + $"uint value2 = (uint)value >> {_rotateBitNum};"); + lines.Add(indent + $"uint part1 = (uint)value << (32 - {_rotateBitNum});"); + lines.Add(indent + $"value = (int)(value2 | part1);"); + lines.Add(indent + $"value = (value - _secretKey[{_index1}]) * {_revertMultipleValue};"); + lines.Add(indent + $"value ^= {_xorValue} ^ salt;"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta new file mode 100644 index 00000000..e95af06a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/Instructions/XorMultipleRotateInstruction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3eb7e6d475cfc14459d3850c5964ba52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs new file mode 100644 index 00000000..1ca1a598 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs @@ -0,0 +1,17 @@ +namespace Obfuz.EncryptionVM +{ + public class VirtualMachine + { + public const int SecretKeyLength = 1024; + + public readonly int version; + public readonly string codeGenerationSecretKey; + public readonly EncryptionInstructionWithOpCode[] opCodes; + + public VirtualMachine(int version, string codeGenerationSecretKey, EncryptionInstructionWithOpCode[] opCodes) + { + this.codeGenerationSecretKey = codeGenerationSecretKey; + this.opCodes = opCodes; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta new file mode 100644 index 00000000..e932d330 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6970e037654dcb49912783a40f3e1ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs new file mode 100644 index 00000000..f12d3688 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs @@ -0,0 +1,203 @@ +using Obfuz.Utils; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace Obfuz.EncryptionVM +{ + public class VirtualMachineCodeGenerator + { + private readonly int _opCodeCount; + private readonly int _opCodeBits; + private readonly VirtualMachine _vm; + + public VirtualMachineCodeGenerator(string vmCodeGenerateSecretKey, int opCodeCount) + { + _opCodeCount = opCodeCount; + _opCodeBits = EncryptionUtil.GetBitCount(opCodeCount - 1); + _vm = new VirtualMachineCreator(vmCodeGenerateSecretKey).CreateVirtualMachine(opCodeCount); + } + + public VirtualMachineCodeGenerator(VirtualMachine vm) + { + _opCodeCount = vm.opCodes.Length; + _opCodeBits = EncryptionUtil.GetBitCount(_opCodeCount - 1); + _vm = vm; + } + + + public bool ValidateMatch(string outputFile) + { + if (!File.Exists(outputFile)) + { + return false; + } + string oldCode = NormalizeText(File.ReadAllText(outputFile, Encoding.UTF8)); + string newCode = NormalizeText(GenerateCode()); + return oldCode == newCode; + } + + private static string NormalizeText(string input) + { + return Regex.Replace(input, @"\s+", string.Empty); + } + + public void Generate(string outputFile) + { + FileUtil.CreateParentDir(outputFile); + + string code = GenerateCode(); + + File.WriteAllText(outputFile, code, Encoding.UTF8); + Debug.Log($"Generate EncryptionVM code to {outputFile}"); + } + + private string GenerateCode() + { + var lines = new List(); + AppendHeader(lines); + AppendEncryptCodes(lines); + AppendDecryptCodes(lines); + AppendTailer(lines); + return string.Join("\n", lines); + } + + private void AppendEncryptCodes(List lines) + { + lines.Add(@" + private int ExecuteEncrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add($@" case {opCode.code}: + {{ + // {opCode.function.GetType().Name}"); + AppendEncryptCode(lines, opCode.function); + lines.Add(@" return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendDecryptCodes(List lines) + { + lines.Add(@" + private int ExecuteDecrypt(int value, int opCode, int salt) + { + switch (opCode) + {"); + foreach (var opCode in _vm.opCodes) + { + lines.Add($@" case {opCode.code}: + {{ + // {opCode.function.GetType().Name}"); + AppendDecryptCode(lines, opCode.function); + lines.Add(@" return value; + }"); + } + + lines.Add(@" + default: + throw new System.Exception($""Invalid opCode:{opCode}""); + } + }"); + } + + private void AppendHeader(List lines) + { + + lines.Add($"/// This file is auto-generated by Obfuz. Do not modify it."); + lines.Add($"///"); + //lines.Add($"/// Created Time: {DateTime.Now}"); + + lines.Add($"/// Version: {_vm.version}"); + lines.Add($"/// SecretKey: {_vm.codeGenerationSecretKey}"); + lines.Add($"/// OpCodeCount: {_vm.opCodes.Length}"); + + lines.Add(@" +namespace Obfuz.EncryptionVM +{ + public class GeneratedEncryptionVirtualMachine : Obfuz.EncryptorBase + {"); + lines.Add($@" + private const int kOpCodeBits = {_opCodeBits}; + + private const int kOpCodeCount = {_opCodeCount}; + + private const int kOpCodeMask = {_opCodeCount - 1}; +"); + lines.Add(@" + + private readonly int[] _secretKey; + + public GeneratedEncryptionVirtualMachine(byte[] secretKey) + { + this._secretKey = ConvertToIntKey(secretKey); + } + + public override int OpCodeCount => kOpCodeCount; + + public override int Encrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + uint revertOps = 0; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + revertOps <<= kOpCodeBits; + revertOps |= opCode; + uopts >>= kOpCodeBits; + } + + while (revertOps != 0) + { + uint opCode = revertOps & kOpCodeMask; + value = ExecuteEncrypt(value, (int)opCode, salt); + revertOps >>= kOpCodeBits; + } + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + uint uopts = (uint)opts; + while (uopts != 0) + { + uint opCode = uopts & kOpCodeMask; + value = ExecuteDecrypt(value, (int)opCode, salt); + uopts >>= kOpCodeBits; + } + return value; + } +"); + } + + private void AppendTailer(List lines) + { + lines.Add(@" + } +} + +"); + } + + private void AppendEncryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateEncryptCode(lines, " "); + } + + private void AppendDecryptCode(List lines, IEncryptionInstruction instruction) + { + instruction.GenerateDecryptCode(lines, " "); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta new file mode 100644 index 00000000..956402cb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2246e9d3369eb3c45bc19ae0748d76ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs new file mode 100644 index 00000000..e0fca883 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs @@ -0,0 +1,68 @@ +using Obfuz.EncryptionVM.Instructions; +using Obfuz.Utils; +using System; +using System.Collections.Generic; + +namespace Obfuz.EncryptionVM +{ + public class VirtualMachineCreator + { + private readonly string _vmGenerationSecretKey; + private readonly IRandom _random; + + public const int CodeGenerationSecretKeyLength = 1024; + + public const int VirtualMachineVersion = 1; + + public VirtualMachineCreator(string vmGenerationSecretKey) + { + _vmGenerationSecretKey = vmGenerationSecretKey; + byte[] byteGenerationSecretKey = KeyGenerator.GenerateKey(vmGenerationSecretKey, CodeGenerationSecretKeyLength); + int[] intGenerationSecretKey = KeyGenerator.ConvertToIntKey(byteGenerationSecretKey); + _random = new RandomWithKey(intGenerationSecretKey, 0); + } + + private readonly List> _instructionCreators = new List> + { + (r, len) => new AddInstruction(r.NextInt(), r.NextInt(len)), + (r, len) => new XorInstruction(r.NextInt(), r.NextInt(len)), + (r, len) => new BitRotateInstruction(r.NextInt(32), r.NextInt(len)), + (r, len) => new MultipleInstruction(r.NextInt() | 0x1, r.NextInt(len)), + (r, len) => new AddRotateXorInstruction(r.NextInt(), r.NextInt(len), r.NextInt(32), r.NextInt()), + (r, len) => new AddXorRotateInstruction(r.NextInt(), r.NextInt(len), r.NextInt(), r.NextInt(32)), + (r, len) => new XorAddRotateInstruction(r.NextInt(), r.NextInt(), r.NextInt(len), r.NextInt(32)), + (r, len) => new MultipleRotateXorInstruction(r.NextInt() | 0x1, r.NextInt(len), r.NextInt(32), r.NextInt()), + (r, len) => new MultipleXorRotateInstruction(r.NextInt() | 0x1, r.NextInt(len), r.NextInt(), r.NextInt(32)), + (r, len) => new XorMultipleRotateInstruction(r.NextInt(), r.NextInt() | 0x1, r.NextInt(len), r.NextInt(32)), + }; + + private IEncryptionInstruction CreateRandomInstruction(int intSecretKeyLength) + { + return _instructionCreators[_random.NextInt(_instructionCreators.Count)](_random, intSecretKeyLength); + } + + private EncryptionInstructionWithOpCode CreateEncryptOpCode(ushort code) + { + IEncryptionInstruction inst = CreateRandomInstruction(VirtualMachine.SecretKeyLength / sizeof(int)); + return new EncryptionInstructionWithOpCode(code, inst); + } + + public VirtualMachine CreateVirtualMachine(int opCodeCount) + { + if (opCodeCount < 64) + { + throw new System.Exception("OpCode count should be >= 64"); + } + if ((opCodeCount & (opCodeCount - 1)) != 0) + { + throw new System.Exception("OpCode count should be power of 2"); + } + var opCodes = new EncryptionInstructionWithOpCode[opCodeCount]; + for (int i = 0; i < opCodes.Length; i++) + { + opCodes[i] = CreateEncryptOpCode((ushort)i); + } + return new VirtualMachine(VirtualMachineVersion, _vmGenerationSecretKey, opCodes); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta new file mode 100644 index 00000000..3124bb7b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77d95ff5cf0b3aa4e96a055e37c381ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs new file mode 100644 index 00000000..175881f2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs @@ -0,0 +1,90 @@ +using Obfuz.Utils; +using System.Collections.Generic; +using UnityEngine.Assertions; + +namespace Obfuz.EncryptionVM +{ + + public class VirtualMachineSimulator : EncryptorBase + { + private readonly EncryptionInstructionWithOpCode[] _opCodes; + private readonly int[] _secretKey; + + public override int OpCodeCount => _opCodes.Length; + + public VirtualMachineSimulator(VirtualMachine vm, byte[] byteSecretKey) + { + _opCodes = vm.opCodes; + _secretKey = KeyGenerator.ConvertToIntKey(byteSecretKey); + + VerifyInstructions(); + } + + private void VerifyInstructions() + { + int value = 0x11223344; + for (int i = 0; i < _opCodes.Length; i++) + { + int encryptedValue = _opCodes[i].Encrypt(value, _secretKey, i); + int decryptedValue = _opCodes[i].Decrypt(encryptedValue, _secretKey, i); + //Debug.Log($"instruction type:{_opCodes[i].function.GetType()}"); + Assert.AreEqual(value, decryptedValue); + } + + int ops = 11223344; + int salt = 789; + Assert.AreEqual(1, Decrypt(Encrypt(1, ops, salt), ops, salt)); + Assert.AreEqual(1L, Decrypt(Encrypt(1L, ops, salt), ops, salt)); + Assert.AreEqual(1.0f, Decrypt(Encrypt(1.0f, ops, salt), ops, salt)); + Assert.AreEqual(1.0, Decrypt(Encrypt(1.0, ops, salt), ops, salt)); + + byte[] strBytes = Encrypt("abcdef", ops, salt); + Assert.AreEqual("abcdef", DecryptString(strBytes, 0, strBytes.Length, ops, salt)); + var arr = new byte[100]; + for (int i = 0; i < arr.Length; i++) + { + arr[i] = (byte)i; + } + EncryptBlock(arr, ops, salt); + DecryptBlock(arr, ops, salt); + for (int i = 0; i < arr.Length; i++) + { + Assert.AreEqual(i, arr[i]); + } + } + + private List DecodeOps(uint ops) + { + var codes = new List(); + while (ops != 0) + { + uint code = ops % (uint)_opCodes.Length; + codes.Add(code); + ops /= (uint)_opCodes.Length; + } + return codes; + } + + public override int Encrypt(int value, int ops, int salt) + { + var codes = DecodeOps((uint)ops); + for (int i = codes.Count - 1; i >= 0; i--) + { + var opCode = _opCodes[codes[i]]; + value = opCode.Encrypt(value, _secretKey, salt); + } + return value; + } + + public override int Decrypt(int value, int ops, int salt) + { + var codes = DecodeOps((uint)ops); + foreach (var code in codes) + { + var opCode = _opCodes[code]; + value = opCode.Decrypt(value, _secretKey, salt); + } + return value; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta new file mode 100644 index 00000000..532a4985 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/EncryptionVM/VirtualMachineSimulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f86f4d6faf49764a915d5c675091375 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration.meta new file mode 100644 index 00000000..29fa1339 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f47f2abd9eb7ba8469ba5cb1bb085d33 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs new file mode 100644 index 00000000..06a7aebb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs @@ -0,0 +1,117 @@ +using Obfuz.Utils; +using System; +using System.Linq; +using System.Text; + +namespace Obfuz.GarbageCodeGeneration +{ + + public class ConfigGarbageCodeGenerator : SpecificGarbageCodeGeneratorBase + { + + private readonly string[] _types = new string[] + { + "bool", + "byte", + "short", + "int", + "long", + "float", + "double", + }; + + private string CreateRandomType(IRandom random) + { + return _types[random.NextInt(_types.Length)]; + } + + private string GetReadMethodNameOfType(string type) + { + switch (type) + { + case "bool": return "ReadBoolean"; + case "byte": return "ReadByte"; + case "short": return "ReadInt16"; + case "int": return "ReadInt32"; + case "long": return "ReadInt64"; + case "float": return "ReadSingle"; + case "double": return "ReadDouble"; + default: throw new ArgumentException($"Unsupported type: {type}"); + } + } + class FieldGenerationInfo + { + public int index; + public string name; + public string type; + } + + class MethodGenerationInfo + { + public int index; + public string name; + } + + protected override object CreateField(int index, IRandom random, GenerationParameters parameters) + { + return new FieldGenerationInfo + { + index = index, + name = $"x{index}", + type = CreateRandomType(random), + }; + } + + protected override object CreateMethod(int index, IRandom random, GenerationParameters parameters) + { + return new MethodGenerationInfo + { + index = index, + name = $"Load{index}", + }; + } + + protected override void GenerateUsings(StringBuilder result, IClassGenerationInfo cgi) + { + } + + protected override void GenerateField(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object field, string indent) + { + var fgi = (FieldGenerationInfo)field; + result.AppendLine($"{indent}public {fgi.type} {fgi.name};"); + } + + protected override void GenerateMethod(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object method, string indent) + { + var mgi = (MethodGenerationInfo)method; + result.AppendLine($"{indent}public void {mgi.name}(BinaryReader reader)"); + result.AppendLine($"{indent}{{"); + + string indent2 = indent + " "; + result.AppendLine($"{indent2}int a = 0;"); + result.AppendLine($"{indent2}int b = 0;"); + int maxN = 100; + var shuffledFields = cgi.Fields.ToList(); + RandomUtil.ShuffleList(shuffledFields, random); + foreach (FieldGenerationInfo fgi in shuffledFields) + { + result.AppendLine($"{indent2}this.{fgi.name} = reader.{GetReadMethodNameOfType(fgi.type)}();"); + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}a = b * {random.NextInt(maxN)} + reader.ReadInt32();"); + result.AppendLine($"{indent2}b = a * reader.ReadInt32() + {random.NextInt(maxN)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}a += {random.NextInt(0, 10000)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}b += {random.NextInt(0, 10000)};"); + } + } + + result.AppendLine($"{indent}}}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs.meta new file mode 100644 index 00000000..c47c0405 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ConfigGarbageCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 327cb4a465ff23944a5fea30bf3beeeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs new file mode 100644 index 00000000..2b00c446 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs @@ -0,0 +1,92 @@ +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.GarbageCodeGeneration +{ + + public class GarbageCodeGenerator + { + private const int CodeGenerationSecretKeyLength = 1024; + + private readonly GarbageCodeGenerationSettings _settings; + private readonly int[] _intGenerationSecretKey; + + public GarbageCodeGenerator(GarbageCodeGenerationSettings settings) + { + _settings = settings; + + byte[] byteGenerationSecretKey = KeyGenerator.GenerateKey(settings.codeGenerationSecret, CodeGenerationSecretKeyLength); + _intGenerationSecretKey = KeyGenerator.ConvertToIntKey(byteGenerationSecretKey); + } + + public void Generate() + { + GenerateTask(_settings.defaultTask); + if (_settings.additionalTasks != null && _settings.additionalTasks.Length > 0) + { + foreach (var task in _settings.additionalTasks) + { + GenerateTask(task); + } + } + } + + public void CleanCodes() + { + Debug.Log($"Cleaning generated garbage codes begin."); + if (_settings.defaultTask != null) + { + FileUtil.RemoveDir(_settings.defaultTask.outputPath, true); + } + if (_settings.additionalTasks != null && _settings.additionalTasks.Length > 0) + { + foreach (var task in _settings.additionalTasks) + { + FileUtil.RemoveDir(task.outputPath, true); + } + } + } + + private void GenerateTask(GarbageCodeGenerationTask task) + { + Debug.Log($"Generating garbage code with seed: {task.codeGenerationRandomSeed}, class count: {task.classCount}, method count per class: {task.methodCountPerClass}, types: {task.garbageCodeType}, output path: {task.outputPath}"); + + if (string.IsNullOrWhiteSpace(task.outputPath)) + { + throw new Exception("outputPath of GarbageCodeGenerationTask is empty!"); + } + + var generator = CreateSpecificCodeGenerator(task.garbageCodeType); + + var parameters = new GenerationParameters + { + random = new RandomWithKey(_intGenerationSecretKey, task.codeGenerationRandomSeed), + classNamespace = task.classNamespace, + classNamePrefix = task.classNamePrefix, + classCount = task.classCount, + methodCountPerClass = task.methodCountPerClass, + fieldCountPerClass = task.fieldCountPerClass, + outputPath = task.outputPath, + }; + generator.Generate(parameters); + + Debug.Log($"Generate garbage code end."); + } + + private ISpecificGarbageCodeGenerator CreateSpecificCodeGenerator(GarbageCodeType type) + { + switch (type) + { + case GarbageCodeType.Config: return new ConfigGarbageCodeGenerator(); + case GarbageCodeType.UI: return new UIGarbageCodeGenerator(); + default: throw new NotSupportedException($"Garbage code type {type} is not supported."); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs.meta new file mode 100644 index 00000000..4939c45d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/GarbageCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff64fd1e6f7b8874db5a5228fab159f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs new file mode 100644 index 00000000..9deda5cc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs @@ -0,0 +1,22 @@ +using Obfuz.Settings; +using Obfuz.Utils; + +namespace Obfuz.GarbageCodeGeneration +{ + public class GenerationParameters + { + public IRandom random; + + public string classNamespace; + public string classNamePrefix; + public int classCount; + public int methodCountPerClass; + public int fieldCountPerClass; + public string outputPath; + } + + public interface ISpecificGarbageCodeGenerator + { + void Generate(GenerationParameters parameters); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs.meta new file mode 100644 index 00000000..9cb265c7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/ISpecificGarbageCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74a17802b5aab2e40a3c89e0ddbcec0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs new file mode 100644 index 00000000..c237343f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs @@ -0,0 +1,106 @@ +using Obfuz.Utils; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEngine; + +namespace Obfuz.GarbageCodeGeneration +{ + public abstract class SpecificGarbageCodeGeneratorBase : ISpecificGarbageCodeGenerator + { + protected interface IClassGenerationInfo + { + string Namespace { get; set; } + + string Name { get; set; } + + IList Fields { get; set; } + + IList Methods { get; set; } + } + + protected class ClassGenerationInfo : IClassGenerationInfo + { + public string Namespace { get; set; } + public string Name { get; set; } + public IList Fields { get; set; } = new List(); + public IList Methods { get; set; } = new List(); + } + + public virtual void Generate(GenerationParameters parameters) + { + FileUtil.RecreateDir(parameters.outputPath); + + for (int i = 0; i < parameters.classCount; i++) + { + Debug.Log($"[{GetType().Name}] Generating class {i}"); + var localRandom = new RandomWithKey(((RandomWithKey)parameters.random).Key, parameters.random.NextInt()); + string outputFile = $"{parameters.outputPath}/__GeneratedGarbageClass_{i}.cs"; + var result = new StringBuilder(64 * 1024); + GenerateClass(i, localRandom, result, parameters); + File.WriteAllText(outputFile, result.ToString(), Encoding.UTF8); + Debug.Log($"[{GetType().Name}] Generated class {i} to {outputFile}"); + } + } + + protected abstract object CreateField(int index, IRandom random, GenerationParameters parameters); + + protected abstract object CreateMethod(int index, IRandom random, GenerationParameters parameters); + + protected virtual IClassGenerationInfo CreateClassGenerationInfo(string classNamespace, string className, IRandom random, GenerationParameters parameters) + { + var cgi = new ClassGenerationInfo + { + Namespace = classNamespace, + Name = className, + }; + + for (int i = 0; i < parameters.fieldCountPerClass; i++) + { + cgi.Fields.Add(CreateField(i, random, parameters)); + } + + for (int i = 0; i < parameters.methodCountPerClass; i++) + { + cgi.Methods.Add(CreateMethod(i, random, parameters)); + } + + return cgi; + } + + protected virtual void GenerateClass(int classIndex, IRandom random, StringBuilder result, GenerationParameters parameters) + { + IClassGenerationInfo cgi = CreateClassGenerationInfo(parameters.classNamespace, $"{parameters.classNamePrefix}{classIndex}", random, parameters); + result.AppendLine("using System;"); + result.AppendLine("using System.Collections.Generic;"); + result.AppendLine("using System.Linq;"); + result.AppendLine("using System.IO;"); + result.AppendLine("using UnityEngine;"); + + GenerateUsings(result, cgi); + + result.AppendLine($"namespace {cgi.Namespace}"); + result.AppendLine("{"); + result.AppendLine($" public class {cgi.Name}"); + result.AppendLine(" {"); + + string indent = " "; + foreach (object field in cgi.Fields) + { + GenerateField(result, cgi, random, field, indent); + } + foreach (object method in cgi.Methods) + { + GenerateMethod(result, cgi, random, method, indent); + } + result.AppendLine(" }"); + result.AppendLine("}"); + } + + protected abstract void GenerateUsings(StringBuilder result, IClassGenerationInfo cgi); + + protected abstract void GenerateField(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object field, string indent); + + protected abstract void GenerateMethod(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object method, string indent); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs.meta new file mode 100644 index 00000000..7696864a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/SpecificGarbageCodeGeneratorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bae18fd49482f00439d37f28a6a78d9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs new file mode 100644 index 00000000..7287dce7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs @@ -0,0 +1,157 @@ +using Obfuz.Utils; +using System; +using System.Linq; +using System.Text; + +namespace Obfuz.GarbageCodeGeneration +{ + + public class UIGarbageCodeGenerator : SpecificGarbageCodeGeneratorBase + { + /* + * + * public Button b1; + public Image b2; + public RawImage b30; + public Text b3; + public Slider b4; + public ScrollRect b5; + public Scrollbar b6; + public Mask b7; + public RectMask2D b70; + public Canvas b8; + public CanvasGroup b9; + public RectTransform b10; + public Transform b11; + public GameObject b12; + */ + + private readonly string[] _types = new string[] + { + "Button", + "Image", + "RawImage", + "Text", + "Slider", + "ScrollRect", + "Scrollbar", + "Mask", + "RectMask2D", + "Canvas", + "CanvasGroup", + "RectTransform", + //"Transform", + //"GameObject", + }; + + private string CreateRandomType(IRandom random) + { + return _types[random.NextInt(_types.Length)]; + } + + private string GetReadMethodNameOfType(string type) + { + switch (type) + { + case "bool": return "ReadBoolean"; + case "byte": return "ReadByte"; + case "short": return "ReadInt16"; + case "int": return "ReadInt32"; + case "long": return "ReadInt64"; + case "float": return "ReadSingle"; + case "double": return "ReadDouble"; + default: throw new ArgumentException($"Unsupported type: {type}"); + } + } + class FieldGenerationInfo + { + public int index; + public string name; + public string type; + } + + class MethodGenerationInfo + { + public int index; + public string name; + } + + protected override object CreateField(int index, IRandom random, GenerationParameters parameters) + { + return new FieldGenerationInfo + { + index = index, + name = $"x{index}", + type = CreateRandomType(random), + }; + } + + protected override object CreateMethod(int index, IRandom random, GenerationParameters parameters) + { + return new MethodGenerationInfo + { + index = index, + name = $"Init{index}", + }; + } + + protected override void GenerateUsings(StringBuilder result, IClassGenerationInfo cgi) + { + result.AppendLine("using UnityEngine.UI;"); + } + + protected override void GenerateField(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object field, string indent) + { + var fgi = (FieldGenerationInfo)field; + result.AppendLine($"{indent}public {fgi.type} {fgi.name};"); + } + + protected override void GenerateMethod(StringBuilder result, IClassGenerationInfo cgi, IRandom random, object method, string indent) + { + var mgi = (MethodGenerationInfo)method; + result.AppendLine($"{indent}public void {mgi.name}(GameObject go)"); + result.AppendLine($"{indent}{{"); + + string indent2 = indent + " "; + result.AppendLine($"{indent2}int a = 0;"); + result.AppendLine($"{indent2}int b = 0;"); + int maxN = 100; + var shuffledFields = cgi.Fields.ToList(); + RandomUtil.ShuffleList(shuffledFields, random); + foreach (FieldGenerationInfo fgi in shuffledFields) + { + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}this.{fgi.name} = go.transform.Find(\"ui/{fgi.name}\").GetComponent<{fgi.type}>();"); + } + else + { + result.AppendLine($"{indent2}this.{fgi.name} = go.GetComponent<{fgi.type}>();"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}a = b * {random.NextInt(maxN)} + go.layer;"); + result.AppendLine($"{indent2}b = a * go.layer + {random.NextInt(maxN)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}a *= {random.NextInt(0, 10000)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}b /= {random.NextInt(0, 10000)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}a = a * b << {random.NextInt(0, 10000)};"); + } + if (random.NextInPercentage(0.5f)) + { + result.AppendLine($"{indent2}b = a / b & {random.NextInt(0, 10000)};"); + } + } + + result.AppendLine($"{indent}}}"); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs.meta new file mode 100644 index 00000000..1dc2d282 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/GarbageCodeGeneration/UIGarbageCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5071c4b9c7f5aef409f3e7fdb45ecd8d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs new file mode 100644 index 00000000..84c56f71 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs @@ -0,0 +1,15 @@ +using Obfuz.ObfusPasses; + +namespace Obfuz +{ + public interface IObfuscationPass + { + ObfuscationPassType Type { get; } + + void Start(); + + void Stop(); + + void Process(); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta new file mode 100644 index 00000000..cd501adc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/IObfuscationPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7003f9503025794b8aa775d9ade335c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses.meta new file mode 100644 index 00000000..ed7f92c1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 120b2dcffd582e84dbb92003240824d1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs new file mode 100644 index 00000000..2b7deb35 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs @@ -0,0 +1,53 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses +{ + public abstract class BasicBlockObfuscationPassBase : ObfuscationMethodPassBase + { + protected virtual bool ComputeBlockInLoop => true; + + protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, + IList globalInstructions, List outputInstructions, List totalFinalInstructions); + + protected override void ObfuscateData(MethodDef method) + { + BasicBlockCollection bbc = new BasicBlockCollection(method, ComputeBlockInLoop); + + IList instructions = method.Body.Instructions; + + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + BasicBlock block = bbc.GetBasicBlockByInstruction(inst); + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, inst, block, i, instructions, outputInstructions, totalFinalInstructions)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta new file mode 100644 index 00000000..027d02b9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae83aaf003665614092aabceabff3cf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta new file mode 100644 index 00000000..a5ed3334 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cf68e45551825c547b137f6e5189937e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs new file mode 100644 index 00000000..6a099f91 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs @@ -0,0 +1,166 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.CallObfus +{ + class ObfusMethodContext + { + public MethodDef method; + public LocalVariableAllocator localVariableAllocator; + public IRandom localRandom; + public EncryptionScopeInfo encryptionScope; + } + + public class CallObfusPass : ObfuscationMethodPassBase + { + public static CallObfuscationSettingsFacade CurrentSettings { get; private set; } + + private readonly CallObfuscationSettingsFacade _settings; + private readonly SpecialWhiteListMethodCalculator _specialWhiteListMethodCache; + + private IObfuscator _dynamicProxyObfuscator; + private IObfuscationPolicy _dynamicProxyPolicy; + + public override ObfuscationPassType Type => ObfuscationPassType.CallObfus; + + public CallObfusPass(CallObfuscationSettingsFacade settings) + { + _settings = settings; + CurrentSettings = settings; + + _specialWhiteListMethodCache = new SpecialWhiteListMethodCalculator(settings.obfuscateCallToMethodInMscorlib); + } + + public override void Stop() + { + _dynamicProxyObfuscator.Done(); + } + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _dynamicProxyObfuscator = CreateObfuscator(ctx, _settings.proxyMode); + _dynamicProxyPolicy = new ConfigurableObfuscationPolicy(ctx.coreSettings.assembliesToObfuscate, _settings.ruleFiles); + } + + private IObfuscator CreateObfuscator(ObfuscationPassContext ctx, ProxyMode mode) + { + switch (mode) + { + case ProxyMode.Dispatch: + return new DispatchProxyObfuscator(ctx.moduleEntityManager); + case ProxyMode.Delegate: + return new DelegateProxyObfuscator(ctx.moduleEntityManager); + default: + throw new System.NotSupportedException($"Unsupported proxy mode: {mode}"); + } + } + + protected override void ObfuscateData(MethodDef method) + { + BasicBlockCollection bbc = new BasicBlockCollection(method, false); + + IList instructions = method.Body.Instructions; + + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + var encryptionScope = ctx.moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + var localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)); + var omc = new ObfusMethodContext + { + method = method, + localVariableAllocator = new LocalVariableAllocator(method), + localRandom = localRandom, + encryptionScope = encryptionScope, + }; + Instruction lastInst = null; + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + BasicBlock block = bbc.GetBasicBlockByInstruction(inst); + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, lastInst, inst, outputInstructions, omc)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + lastInst = inst; + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _dynamicProxyPolicy.NeedObfuscateCallInMethod(method); + } + + private bool TryObfuscateInstruction(MethodDef callerMethod, Instruction lastInst, Instruction inst, List outputInstructions, ObfusMethodContext ctx) + { + IMethod calledMethod = inst.Operand as IMethod; + if (calledMethod == null || !calledMethod.IsMethod) + { + return false; + } + if (MetaUtil.ContainsContainsGenericParameter(calledMethod)) + { + return false; + } + + bool callVir; + switch (inst.OpCode.Code) + { + case Code.Call: + { + callVir = false; + break; + } + case Code.Callvirt: + { + if (lastInst != null && lastInst.OpCode.Code == Code.Constrained) + { + return false; + } + callVir = true; + break; + } + default: return false; + } + + + if (_specialWhiteListMethodCache.IsInWhiteList(calledMethod)) + { + return false; + } + + + if (!_dynamicProxyPolicy.NeedObfuscateCalledMethod(callerMethod, calledMethod, callVir)) + { + return false; + } + + return _dynamicProxyObfuscator.Obfuscate(callerMethod, calledMethod, callVir, outputInstructions); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta new file mode 100644 index 00000000..b15eb819 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/CallObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 112178b770868274fb8119a4997a3420 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 00000000..57a30e58 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,300 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + class WhiteListAssembly + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + public List types = new List(); + } + + class WhiteListType + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + public List methods = new List(); + } + + class WhiteListMethod + { + public string name; + public NameMatcher nameMatcher; + public bool? obfuscate; + } + + class ObfuscationRule : IRule + { + public ObfuscationLevel? obfuscationLevel; + + public void InheritParent(ObfuscationRule parentRule) + { + if (obfuscationLevel == null) + obfuscationLevel = parentRule.obfuscationLevel; + } + } + + class AssemblySpec : AssemblyRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class MethodSpec : MethodRuleBase + { + + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + obfuscationLevel = ObfuscationLevel.Basic, + }; + + private readonly XmlAssemblyTypeMethodRuleParser _configParser; + + private ObfuscationRule _global; + private readonly List _whiteListAssemblies = new List(); + + private readonly CachedDictionary _whiteListMethodCache; + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _whiteListMethodCache = new CachedDictionary(MethodEqualityComparer.CompareDeclaringTypes, this.ComputeIsInWhiteList); + _configParser = new XmlAssemblyTypeMethodRuleParser(toObfuscatedAssemblyNames, + ParseObfuscationRule, ParseGlobalElement); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _configParser.LoadConfigs(configFiles); + + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _configParser.InheritParentRules(_global); + InheritWhitelistRules(); + } + + private void InheritWhitelistRules() + { + foreach (var ass in _whiteListAssemblies) + { + foreach (var type in ass.types) + { + if (type.obfuscate == null) + { + type.obfuscate = ass.obfuscate; + } + foreach (var method in type.methods) + { + if (method.obfuscate == null) + { + method.obfuscate = type.obfuscate; + } + } + } + } + } + + private void ParseGlobalElement(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + case "whitelist": ParseWhitelist(ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("obfuscationLevel")) + { + rule.obfuscationLevel = ConfigUtil.ParseObfuscationLevel(ele.GetAttribute("obfuscationLevel")); + } + return rule; + } + + private void ParseWhitelist(XmlElement ruleEle) + { + foreach (XmlNode xmlNode in ruleEle.ChildNodes) + { + if (!(xmlNode is XmlElement childEle)) + { + continue; + } + switch (childEle.Name) + { + case "assembly": + { + var ass = ParseWhiteListAssembly(childEle); + _whiteListAssemblies.Add(ass); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {childEle.Name}"); + } + } + } + + private WhiteListAssembly ParseWhiteListAssembly(XmlElement element) + { + var ass = new WhiteListAssembly(); + ass.name = element.GetAttribute("name"); + ass.nameMatcher = new NameMatcher(ass.name); + + ass.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")) ?? false; + + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "type": + ass.types.Add(ParseWhiteListType(ele)); + break; + default: + throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + return ass; + } + + private WhiteListType ParseWhiteListType(XmlElement element) + { + var type = new WhiteListType(); + type.name = element.GetAttribute("name"); + type.nameMatcher = new NameMatcher(type.name); + type.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")); + + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement ele)) + { + continue; + } + switch (ele.Name) + { + case "method": + { + type.methods.Add(ParseWhiteListMethod(ele)); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {ele.Name}"); + } + } + + return type; + } + + private WhiteListMethod ParseWhiteListMethod(XmlElement element) + { + var method = new WhiteListMethod(); + method.name = element.GetAttribute("name"); + method.nameMatcher = new NameMatcher(method.name); + method.obfuscate = ConfigUtil.ParseNullableBool(element.GetAttribute("obfuscate")); + return method; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _configParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscateCallInMethod(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.obfuscationLevel != null && rule.obfuscationLevel.Value >= ObfuscationLevel.Basic; + } + + private bool ComputeIsInWhiteList(IMethod calledMethod) + { + ITypeDefOrRef declaringType = calledMethod.DeclaringType; + TypeSig declaringTypeSig = calledMethod.DeclaringType.ToTypeSig(); + declaringTypeSig = declaringTypeSig.RemovePinnedAndModifiers(); + switch (declaringTypeSig.ElementType) + { + case ElementType.ValueType: + case ElementType.Class: + { + break; + } + case ElementType.GenericInst: + { + if (MetaUtil.ContainsContainsGenericParameter(calledMethod)) + { + return true; + } + break; + } + default: return true; + } + + TypeDef typeDef = declaringType.ResolveTypeDef(); + + string assName = typeDef.Module.Assembly.Name; + string typeFullName = typeDef.FullName; + string methodName = calledMethod.Name; + foreach (var ass in _whiteListAssemblies) + { + if (!ass.nameMatcher.IsMatch(assName)) + { + continue; + } + foreach (var type in ass.types) + { + if (!type.nameMatcher.IsMatch(typeFullName)) + { + continue; + } + foreach (var method in type.methods) + { + if (method.nameMatcher.IsMatch(methodName)) + { + return !method.obfuscate.Value; + } + } + return !type.obfuscate.Value; + } + return !ass.obfuscate.Value; + } + return false; + } + + public override bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir) + { + if (_whiteListMethodCache.GetValue(calledMethod)) + { + return false; + } + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 00000000..c970eec6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9ea12b16c4b296459db8a60fb1615d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs new file mode 100644 index 00000000..7b9b6e04 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs @@ -0,0 +1,263 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + struct DelegateProxyMethodData + { + public readonly FieldDef delegateInstanceField; + public readonly MethodDef delegateInvokeMethod; + + public DelegateProxyMethodData(FieldDef delegateInstanceField, MethodDef delegateInvokeMethod) + { + this.delegateInstanceField = delegateInstanceField; + this.delegateInvokeMethod = delegateInvokeMethod; + } + } + + class DelegateProxyAllocator : GroupByModuleEntityBase + { + private readonly CachedDictionary _delegateTypes; + private readonly HashSet _allocatedDelegateNames = new HashSet(); + + private TypeDef _delegateInstanceHolderType; + private bool _done; + + class CallInfo + { + public string key1; + public int key2; + public IMethod method; + public bool callVir; + + public int index; + public TypeDef delegateType; + public FieldDef delegateInstanceField; + public MethodDef delegateInvokeMethod; + public MethodDef proxyMethod; + } + private readonly Dictionary _callMethods = new Dictionary(); + + public DelegateProxyAllocator() + { + _delegateTypes = new CachedDictionary(SignatureEqualityComparer.Instance, CreateDelegateForSignature); + } + + public override void Init() + { + _delegateInstanceHolderType = CreateDelegateInstanceHolderTypeDef(); + } + + private string AllocateDelegateTypeName(MethodSig delegateInvokeSig) + { + uint hashCode = (uint)SignatureEqualityComparer.Instance.GetHashCode(delegateInvokeSig); + string typeName = $"$Obfuz$Delegate_{hashCode}"; + if (_allocatedDelegateNames.Add(typeName)) + { + return typeName; + } + for (int i = 0; ;i++) + { + typeName = $"$Obfuz$Delegate_{hashCode}_{i}"; + if (_allocatedDelegateNames.Add(typeName)) + { + return typeName; + } + } + } + + private TypeDef CreateDelegateForSignature(MethodSig delegateInvokeSig) + { + ModuleDef mod = Module; + using (var scope = new DisableTypeDefFindCacheScope(mod)) + { + + string typeName = AllocateDelegateTypeName(delegateInvokeSig); + mod.Import(typeof(MulticastDelegate)); + + TypeDef delegateType = new TypeDefUser("", typeName, mod.CorLibTypes.GetTypeRef("System", "MulticastDelegate")); + delegateType.Attributes = TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public; + mod.Types.Add(delegateType); + + MethodDef ctor = new MethodDefUser( + ".ctor", + MethodSig.CreateInstance(mod.CorLibTypes.Void, mod.CorLibTypes.Object, mod.CorLibTypes.IntPtr), + MethodImplAttributes.Runtime, + MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public + ); + ctor.DeclaringType = delegateType; + + + MethodDef invokeMethod = new MethodDefUser( + "Invoke", + MethodSig.CreateInstance(delegateInvokeSig.RetType, delegateInvokeSig.Params.ToArray()), + MethodImplAttributes.Runtime, + MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Virtual + ); + invokeMethod.DeclaringType = delegateType; + return delegateType; + } + } + + private TypeDef CreateDelegateInstanceHolderTypeDef() + { + ModuleDef mod = Module; + using (var scope = new DisableTypeDefFindCacheScope(mod)) + { + string typeName = "$Obfuz$DelegateInstanceHolder"; + TypeDef holderType = new TypeDefUser("", typeName, mod.CorLibTypes.Object.ToTypeDefOrRef()); + holderType.Attributes = TypeAttributes.Class | TypeAttributes.Public; + mod.Types.Add(holderType); + return holderType; + } + } + + private string AllocateFieldName(IMethod method, bool callVir) + { + uint hashCode = (uint)MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method); + string typeName = $"$Obfuz$Delegate$Field_{hashCode}_{callVir}"; + if (_allocatedDelegateNames.Add(typeName)) + { + return typeName; + } + for (int i = 0; ; i++) + { + typeName = $"$Obfuz$Delegate$Field_{hashCode}_{callVir}_{i}"; + if (_allocatedDelegateNames.Add(typeName)) + { + return typeName; + } + } + } + + private MethodDef CreateProxyMethod(string name, IMethod calledMethod, bool callVir, MethodSig delegateInvokeSig) + { + var proxyMethod = new MethodDefUser(name, delegateInvokeSig, MethodImplAttributes.Managed, MethodAttributes.Public | MethodAttributes.Static); + var body = new CilBody(); + proxyMethod.Body = body; + var ins = body.Instructions; + + foreach (Parameter param in proxyMethod.Parameters) + { + ins.Add(Instruction.Create(OpCodes.Ldarg, param)); + } + + ins.Add(Instruction.Create(callVir ? OpCodes.Callvirt : OpCodes.Call, calledMethod)); + ins.Add(Instruction.Create(OpCodes.Ret)); + return proxyMethod; + } + + public DelegateProxyMethodData Allocate(IMethod method, bool callVir, MethodSig delegateInvokeSig) + { + var key = new MethodKey(method, callVir); + if (!_callMethods.TryGetValue(key, out var callInfo)) + { + TypeDef delegateType = _delegateTypes.GetValue(delegateInvokeSig); + MethodDef delegateInvokeMethod = delegateType.FindMethod("Invoke"); + string fieldName = AllocateFieldName(method, callVir); + FieldDef delegateInstanceField = new FieldDefUser(fieldName, new FieldSig(delegateType.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); + string key1 = $"{method.FullName}_{callVir}"; + callInfo = new CallInfo + { + key1 = key1, + key2 = HashUtil.ComputePrimitiveOrStringOrBytesHashCode(key1) * 33445566, + method = method, + callVir = callVir, + delegateType = delegateType, + delegateInstanceField = delegateInstanceField, + delegateInvokeMethod = delegateInvokeMethod, + proxyMethod = CreateProxyMethod($"{fieldName}$Proxy", method, callVir, delegateInvokeSig), + }; + _callMethods.Add(key, callInfo); + } + return new DelegateProxyMethodData(callInfo.delegateInstanceField, callInfo.delegateInvokeMethod); + } + + public override void Done() + { + if (_done) + { + throw new Exception("Already done"); + } + _done = true; + + ModuleDef mod = Module; + + // for stable order, we sort methods by name + List callMethodList = _callMethods.Values.ToList(); + callMethodList.Sort((a, b) => a.key1.CompareTo(b.key1)); + + var cctor = new MethodDefUser(".cctor", + MethodSig.CreateStatic(mod.CorLibTypes.Void), + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Private); + cctor.DeclaringType = _delegateInstanceHolderType; + //_rvaTypeDef.Methods.Add(cctor); + var body = new CilBody(); + cctor.Body = body; + var ins = body.Instructions; + + // var arr = new array[]; + // var d = new delegate; + // arr[index] = d; + int index = 0; + ins.Add(Instruction.CreateLdcI4(callMethodList.Count)); + ins.Add(Instruction.Create(OpCodes.Newarr, mod.CorLibTypes.Object)); + foreach (CallInfo ci in callMethodList) + { + ci.index = index; + _delegateInstanceHolderType.Methods.Add(ci.proxyMethod); + ins.Add(Instruction.Create(OpCodes.Dup)); + ins.Add(Instruction.CreateLdcI4(index)); + ins.Add(Instruction.Create(OpCodes.Ldnull)); + ins.Add(Instruction.Create(OpCodes.Ldftn, ci.proxyMethod)); + MethodDef ctor = ci.delegateType.FindMethod(".ctor"); + UnityEngine.Assertions.Assert.IsNotNull(ctor, $"Delegate type {ci.delegateType.FullName} does not have a constructor."); + ins.Add(Instruction.Create(OpCodes.Newobj, ctor)); + ins.Add(Instruction.Create(OpCodes.Stelem_Ref)); + ++index; + } + + + + List callMethodList2 = callMethodList.ToList(); + callMethodList2.Sort((a, b) => a.key2.CompareTo(b.key2)); + + EncryptionScopeInfo encryptionScope = EncryptionScope; + DefaultMetadataImporter importer = this.GetDefaultModuleMetadataImporter(); + RvaDataAllocator rvaDataAllocator = this.GetEntity(); + foreach (CallInfo ci in callMethodList2) + { + _delegateInstanceHolderType.Fields.Add(ci.delegateInstanceField); + + + ins.Add(Instruction.Create(OpCodes.Dup)); + + IRandom localRandom = encryptionScope.localRandomCreator(HashUtil.ComputePrimitiveOrStringOrBytesHashCode(ci.key1)); + int ops = EncryptionUtil.GenerateEncryptionOpCodes(localRandom, encryptionScope.encryptor, 4); + int salt = localRandom.NextInt(); + + int encryptedValue = encryptionScope.encryptor.Encrypt(ci.index, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + ins.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + ins.Add(Instruction.CreateLdcI4(rvaData.offset)); + ins.Add(Instruction.CreateLdcI4(ops)); + ins.Add(Instruction.CreateLdcI4(salt)); + ins.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt)); + ins.Add(Instruction.Create(OpCodes.Ldelem_Ref)); + ins.Add(Instruction.Create(OpCodes.Stsfld, ci.delegateInstanceField)); + } + + ins.Add(Instruction.Create(OpCodes.Pop)); + ins.Add(Instruction.Create(OpCodes.Ret)); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs.meta new file mode 100644 index 00000000..d324d9aa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02761bacbed8a8b489ae3e7f49f0f84a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs new file mode 100644 index 00000000..bc6ce18c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs @@ -0,0 +1,83 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + public class DelegateProxyObfuscator : ObfuscatorBase + { + private readonly GroupByModuleEntityManager _entityManager; + + public DelegateProxyObfuscator(GroupByModuleEntityManager moduleEntityManager) + { + _entityManager = moduleEntityManager; + } + + public override void Done() + { + _entityManager.Done(); + } + + private MethodSig CreateProxyMethodSig(ModuleDef module, IMethod method) + { + MethodSig methodSig = MetaUtil.ToSharedMethodSig(module.CorLibTypes, MetaUtil.GetInflatedMethodSig(method, null)); + //MethodSig methodSig = MetaUtil.GetInflatedMethodSig(method).Clone(); + //methodSig.Params + switch (MetaUtil.GetThisArgType(method)) + { + case ThisArgType.Class: + { + methodSig.Params.Insert(0, module.CorLibTypes.Object); + break; + } + case ThisArgType.ValueType: + { + methodSig.Params.Insert(0, module.CorLibTypes.IntPtr); + break; + } + } + return MethodSig.CreateStatic(methodSig.RetType, methodSig.Params.ToArray()); + } + + public override bool Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions) + { + DelegateProxyAllocator allocator = _entityManager.GetEntity(callingMethod.Module); + LocalVariableAllocator localVarAllocator = new LocalVariableAllocator(callingMethod); + MethodSig methodSig = CreateProxyMethodSig(callingMethod.Module, calledMethod); + DelegateProxyMethodData proxyData = allocator.Allocate(calledMethod, callVir, methodSig); + bool isVoidReturn = MetaUtil.IsVoidType(methodSig.RetType); + + using (var varScope = localVarAllocator.CreateScope()) + { + List localVars = new List(); + if (!isVoidReturn) + { + varScope.AllocateLocal(methodSig.RetType); + } + foreach (var p in methodSig.Params) + { + localVars.Add(varScope.AllocateLocal(p)); + } + // save args + for (int i = localVars.Count - 1; i >= 0; i--) + { + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Stloc, localVars[i])); + } + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, proxyData.delegateInstanceField)); + foreach (var local in localVars) + { + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldloc, local)); + } + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Callvirt, proxyData.delegateInvokeMethod)); + } + + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs.meta new file mode 100644 index 00000000..4cb75c37 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DelegateProxyObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1102cd9f03de27c4b9fde3d6a87277c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs new file mode 100644 index 00000000..f4fe3bc8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs @@ -0,0 +1,273 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Editor; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using MethodImplAttributes = dnlib.DotNet.MethodImplAttributes; +using TypeAttributes = dnlib.DotNet.TypeAttributes; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + public struct ProxyCallMethodData + { + public readonly MethodDef proxyMethod; + public readonly int encryptOps; + public readonly int salt; + public readonly int encryptedIndex; + public readonly int index; + + public ProxyCallMethodData(MethodDef proxyMethod, int encryptOps, int salt, int encryptedIndex, int index) + { + this.proxyMethod = proxyMethod; + this.encryptOps = encryptOps; + this.salt = salt; + this.encryptedIndex = encryptedIndex; + this.index = index; + } + } + + class ModuleDispatchProxyAllocator : GroupByModuleEntityBase + { + private bool _done; + private CallObfuscationSettingsFacade _settings; + + + class MethodProxyInfo + { + public MethodDef proxyMethod; + + public int index; + public int encryptedOps; + public int salt; + public int encryptedIndex; + } + + private readonly Dictionary _methodProxys = new Dictionary(); + + class CallInfo + { + public string id; + public IMethod method; + public bool callVir; + } + + class DispatchMethodInfo + { + public MethodDef methodDef; + public List methods = new List(); + } + + private readonly Dictionary> _dispatchMethods = new Dictionary>(SignatureEqualityComparer.Instance); + + + private TypeDef _proxyTypeDef; + + public ModuleDispatchProxyAllocator() + { + } + + public override void Init() + { + _settings = CallObfusPass.CurrentSettings; + } + + private TypeDef CreateProxyTypeDef() + { + ModuleDef mod = Module; + using (var scope = new DisableTypeDefFindCacheScope(mod)) + { + var typeDef = new TypeDefUser($"{ConstValues.ObfuzInternalSymbolNamePrefix}ProxyCall", mod.CorLibTypes.Object.ToTypeDefOrRef()); + typeDef.Attributes = TypeAttributes.NotPublic | TypeAttributes.Sealed; + mod.Types.Add(typeDef); + return typeDef; + } + } + + private readonly HashSet _uniqueMethodNames = new HashSet(); + + + private string ToUniqueMethodName(string originalName) + { + if (_uniqueMethodNames.Add(originalName)) + { + return originalName; + } + for (int index = 1; ; index++) + { + string uniqueName = $"{originalName}${index}"; + if (_uniqueMethodNames.Add(uniqueName)) + { + return uniqueName; + } + } + } + + private string CreateDispatchMethodName(MethodSig methodSig, int index) + { + // use a stable name for the dispatch method, so that we can reuse it across different modules + // this is important for cross-module calls + return ToUniqueMethodName($"{ConstValues.ObfuzInternalSymbolNamePrefix}Dispatch_{HashUtil.ComputeHash(methodSig.Params) & 0xFFFF}_{HashUtil.ComputeHash(methodSig.RetType) & 0xFFFFFF}"); + } + + private MethodDef CreateDispatchMethodInfo(MethodSig methodSig, int index) + { + if (_proxyTypeDef == null) + { + _proxyTypeDef = CreateProxyTypeDef(); + } + MethodDef methodDef = new MethodDefUser(CreateDispatchMethodName(methodSig, index), methodSig, + MethodImplAttributes.IL | MethodImplAttributes.Managed, + MethodAttributes.Static | MethodAttributes.Public); + methodDef.DeclaringType = _proxyTypeDef; + return methodDef; + } + + private MethodSig CreateDispatchMethodSig(IMethod method) + { + ModuleDef mod = Module; + MethodSig methodSig = MetaUtil.ToSharedMethodSig(mod.CorLibTypes, MetaUtil.GetInflatedMethodSig(method, null)); + //MethodSig methodSig = MetaUtil.GetInflatedMethodSig(method).Clone(); + //methodSig.Params + switch (MetaUtil.GetThisArgType(method)) + { + case ThisArgType.Class: + { + methodSig.Params.Insert(0, mod.CorLibTypes.Object); + break; + } + case ThisArgType.ValueType: + { + methodSig.Params.Insert(0, mod.CorLibTypes.IntPtr); + break; + } + } + // extra param for index + methodSig.Params.Add(mod.CorLibTypes.Int32); + return MethodSig.CreateStatic(methodSig.RetType, methodSig.Params.ToArray()); + } + + private int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private int GenerateEncryptOps(IRandom random) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, EncryptionScope.encryptor, _settings.obfuscationLevel); + } + + private DispatchMethodInfo GetDispatchMethod(IMethod method) + { + MethodSig methodSig = CreateDispatchMethodSig(method); + if (!_dispatchMethods.TryGetValue(methodSig, out var dispatchMethods)) + { + dispatchMethods = new List(); + _dispatchMethods.Add(methodSig, dispatchMethods); + } + if (dispatchMethods.Count == 0 || dispatchMethods.Last().methods.Count >= _settings.maxProxyMethodCountPerDispatchMethod) + { + var newDispatchMethodInfo = new DispatchMethodInfo + { + methodDef = CreateDispatchMethodInfo(methodSig, dispatchMethods.Count), + }; + dispatchMethods.Add(newDispatchMethodInfo); + } + return dispatchMethods.Last(); + } + + private IRandom CreateRandomForMethod(IMethod method, bool callVir) + { + int seed = MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method); + return EncryptionScope.localRandomCreator(seed); + } + + public ProxyCallMethodData Allocate(IMethod method, bool callVir) + { + if (_done) + { + throw new Exception("can't Allocate after done"); + } + var key = new MethodKey(method, callVir); + if (!_methodProxys.TryGetValue(key, out var proxyInfo)) + { + var methodDispatcher = GetDispatchMethod(method); + + int index = methodDispatcher.methods.Count; + IRandom localRandom = CreateRandomForMethod(method, callVir); + int encryptOps = GenerateEncryptOps(localRandom); + int salt = GenerateSalt(localRandom); + int encryptedIndex = EncryptionScope.encryptor.Encrypt(index, encryptOps, salt); + proxyInfo = new MethodProxyInfo() + { + proxyMethod = methodDispatcher.methodDef, + index = index, + encryptedOps = encryptOps, + salt = salt, + encryptedIndex = encryptedIndex, + }; + methodDispatcher.methods.Add(new CallInfo { id = $"{method}{(callVir ? "" : "v")}", method = method, callVir = callVir }); + _methodProxys.Add(key, proxyInfo); + } + return new ProxyCallMethodData(proxyInfo.proxyMethod, proxyInfo.encryptedOps, proxyInfo.salt, proxyInfo.encryptedIndex, proxyInfo.index); + } + + public override void Done() + { + if (_done) + { + throw new Exception("Already done"); + } + _done = true; + if (_proxyTypeDef == null) + { + return; + } + + // for stable order, we sort methods by name + var methodWithNamePairList = _proxyTypeDef.Methods.Select(m => (m, m.ToString())).ToList(); + methodWithNamePairList.Sort((a, b) => a.Item2.CompareTo(b.Item2)); + _proxyTypeDef.Methods.Clear(); + foreach (var methodPair in methodWithNamePairList) + { + methodPair.Item1.DeclaringType = _proxyTypeDef; + } + + foreach (DispatchMethodInfo dispatchMethod in _dispatchMethods.Values.SelectMany(ms => ms)) + { + var methodDef = dispatchMethod.methodDef; + var methodSig = methodDef.MethodSig; + + + var body = new CilBody(); + methodDef.Body = body; + var ins = body.Instructions; + + foreach (Parameter param in methodDef.Parameters) + { + ins.Add(Instruction.Create(OpCodes.Ldarg, param)); + } + + var switchCases = new List(); + var switchInst = Instruction.Create(OpCodes.Switch, switchCases); + ins.Add(switchInst); + var ret = Instruction.Create(OpCodes.Ret); + + // sort methods by signature to ensure stable order + //dispatchMethod.methods.Sort((a, b) => a.id.CompareTo(b.id)); + foreach (CallInfo ci in dispatchMethod.methods) + { + var callTargetMethod = Instruction.Create(ci.callVir ? OpCodes.Callvirt : OpCodes.Call, ci.method); + switchCases.Add(callTargetMethod); + ins.Add(callTargetMethod); + ins.Add(Instruction.Create(OpCodes.Br, ret)); + } + ins.Add(ret); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs.meta new file mode 100644 index 00000000..a6a06f88 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16b960455f093854d927c2dbd47a4826 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs new file mode 100644 index 00000000..bc54a774 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs @@ -0,0 +1,53 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + public class DispatchProxyObfuscator : ObfuscatorBase + { + private readonly GroupByModuleEntityManager _moduleEntityManager; + + public DispatchProxyObfuscator(GroupByModuleEntityManager moduleEntityManager) + { + _moduleEntityManager = moduleEntityManager; + } + + public override void Done() + { + _moduleEntityManager.Done(); + } + + public override bool Obfuscate(MethodDef callerMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions) + { + ModuleDispatchProxyAllocator proxyCallAllocator = _moduleEntityManager.GetEntity(callerMethod.Module); + MethodSig sharedMethodSig = MetaUtil.ToSharedMethodSig(calledMethod.Module.CorLibTypes, MetaUtil.GetInflatedMethodSig(calledMethod, null)); + ProxyCallMethodData proxyCallMethodData = proxyCallAllocator.Allocate(calledMethod, callVir); + DefaultMetadataImporter importer = proxyCallAllocator.GetDefaultModuleMetadataImporter(); + + //if (needCacheCall) + //{ + // FieldDef cacheField = _constFieldAllocator.Allocate(callerMethod.Module, proxyCallMethodData.index); + // obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + //} + //else + //{ + // obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.encryptedIndex)); + // obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.encryptOps)); + // obfuscatedInstructions.Add(Instruction.CreateLdcI4(proxyCallMethodData.salt)); + // obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptInt)); + //} + + ConstFieldAllocator constFieldAllocator = proxyCallAllocator.GetEntity(); + FieldDef cacheField = constFieldAllocator.Allocate(proxyCallMethodData.index); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, proxyCallMethodData.proxyMethod)); + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs.meta new file mode 100644 index 00000000..78ae2a27 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/DispatchProxyObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e13ba01b03439e049af0e09367825cde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs new file mode 100644 index 00000000..941291ba --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs @@ -0,0 +1,19 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.CallObfus +{ + + public interface IObfuscationPolicy + { + bool NeedObfuscateCallInMethod(MethodDef method); + + bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir); + } + + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscateCallInMethod(MethodDef method); + + public abstract bool NeedObfuscateCalledMethod(MethodDef callerMethod, IMethod calledMethod, bool callVir); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta new file mode 100644 index 00000000..96009d14 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6af3cd881fdefd14d9a55b77088dd5a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs new file mode 100644 index 00000000..bd26274e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.CallObfus +{ + public interface IObfuscator + { + bool Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions); + + void Done(); + } + + public abstract class ObfuscatorBase : IObfuscator + { + public abstract bool Obfuscate(MethodDef callingMethod, IMethod calledMethod, bool callVir, List obfuscatedInstructions); + + public abstract void Done(); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta new file mode 100644 index 00000000..c75fd401 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4156317478f8b1d438ef6d5a280d409f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs new file mode 100644 index 00000000..99275581 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs @@ -0,0 +1,30 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; + +namespace Obfuz.ObfusPasses.CallObfus +{ + class MethodKey : IEquatable + { + public readonly IMethod _method; + public readonly bool _callVir; + private readonly int _hashCode; + + public MethodKey(IMethod method, bool callVir) + { + _method = method; + _callVir = callVir; + _hashCode = HashUtil.CombineHash(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method), callVir ? 1 : 0); + } + + public override int GetHashCode() + { + return _hashCode; + } + + public bool Equals(MethodKey other) + { + return MethodEqualityComparer.CompareDeclaringTypes.Equals(_method, other._method) && _callVir == other._callVir; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs.meta new file mode 100644 index 00000000..f7a1a2a8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/MethodKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1193647b317b56f4b83aa080d0a17f7a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs new file mode 100644 index 00000000..5f52792b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs @@ -0,0 +1,122 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.CallObfus +{ + class SpecialWhiteListMethodCalculator + { + private readonly bool _obfuscateCallToMethodInMscorlib; + private readonly CachedDictionary _specialWhiteListMethodCache; + + public SpecialWhiteListMethodCalculator(bool obfuscateCallToMethodInMscorlib) + { + _obfuscateCallToMethodInMscorlib = obfuscateCallToMethodInMscorlib; + _specialWhiteListMethodCache = new CachedDictionary(MethodEqualityComparer.CompareDeclaringTypes, this.ComputeIsInWhiteList); + } + + public bool IsInWhiteList(IMethod calledMethod) + { + return _specialWhiteListMethodCache.GetValue(calledMethod); + } + + private static readonly HashSet _specialTypeFullNames = new HashSet + { + "System.Enum", + "System.Delegate", + "System.MulticastDelegate", + "Obfuz.EncryptionService`1", + }; + + private static readonly HashSet _specialMethodNames = new HashSet + { + "GetEnumerator", // List.Enumerator.GetEnumerator() + ".ctor", // constructor + }; + + private static readonly HashSet _specialMethodFullNames = new HashSet + { + "System.Reflection.MethodBase.GetCurrentMethod", + "System.Reflection.Assembly.GetCallingAssembly", + "System.Reflection.Assembly.GetExecutingAssembly", + "System.Reflection.Assembly.GetEntryAssembly", + }; + + private bool ComputeIsInWhiteList(IMethod calledMethod) + { + MethodDef calledMethodDef = calledMethod.ResolveMethodDef(); + // mono has more strict access control, calls non-public method will raise exception. + if (PlatformUtil.IsMonoBackend()) + { + if (calledMethodDef != null && (!calledMethodDef.IsPublic || !IsTypeSelfAndParentPublic(calledMethodDef.DeclaringType))) + { + return true; + } + } + + ITypeDefOrRef declaringType = calledMethod.DeclaringType; + TypeSig declaringTypeSig = calledMethod.DeclaringType.ToTypeSig(); + declaringTypeSig = declaringTypeSig.RemovePinnedAndModifiers(); + switch (declaringTypeSig.ElementType) + { + case ElementType.ValueType: + case ElementType.Class: + { + break; + } + case ElementType.GenericInst: + { + if (MetaUtil.ContainsContainsGenericParameter(calledMethod)) + { + return true; + } + break; + } + default: return true; + } + + TypeDef typeDef = declaringType.ResolveTypeDef(); + + if (!_obfuscateCallToMethodInMscorlib && typeDef.Module.IsCoreLibraryModule == true) + { + return true; + } + + if (typeDef.IsDelegate || typeDef.IsEnum) + return true; + + string fullName = typeDef.FullName; + if (_specialTypeFullNames.Contains(fullName)) + { + return true; + } + //if (fullName.StartsWith("System.Runtime.CompilerServices.")) + //{ + // return true; + //} + + string methodName = calledMethod.Name; + if (_specialMethodNames.Contains(methodName)) + { + return true; + } + + string methodFullName = $"{fullName}.{methodName}"; + if (_specialMethodFullNames.Contains(methodFullName)) + { + return true; + } + return false; + } + + private bool IsTypeSelfAndParentPublic(TypeDef type) + { + if (type.DeclaringType != null && !IsTypeSelfAndParentPublic(type.DeclaringType)) + { + return false; + } + + return type.IsPublic; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs.meta new file mode 100644 index 00000000..b1228ec4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CallObfus/SpecialWhiteListMethodCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 904e80c4b98911c40b6a9173ca24f3ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta new file mode 100644 index 00000000..37096d79 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2764442d8fc2b914dbc39dcfa2699698 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs new file mode 100644 index 00000000..c95854d0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs @@ -0,0 +1,41 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace Obfuz.ObfusPasses.CleanUp +{ + public class CleanUpInstructionPass : ObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.None; + + public override void Start() + { + } + + public override void Stop() + { + + } + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + foreach (ModuleDef mod in ctx.modulesToObfuscate) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.HasBody) + { + CilBody body = method.Body; + body.SimplifyBranches(); + body.OptimizeMacros(); + body.OptimizeBranches(); + // TODO remove dup + } + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta new file mode 100644 index 00000000..cc974323 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/CleanUpInstructionPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78cc056cd929d70409a0f0737b571a6d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs new file mode 100644 index 00000000..6b1ac2bb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs @@ -0,0 +1,67 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.CleanUp +{ + public class RemoveObfuzAttributesPass : ObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.None; + + public override void Start() + { + } + + public override void Stop() + { + + } + + + private void RemoveObfuzAttributes(IHasCustomAttribute provider) + { + CustomAttributeCollection customAttributes = provider.CustomAttributes; + if (customAttributes.Count == 0) + return; + var toRemove = new List(); + customAttributes.RemoveAll(ConstValues.ObfuzIgnoreAttributeFullName); + customAttributes.RemoveAll(ConstValues.EncryptFieldAttributeFullName); + } + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + foreach (ModuleDef mod in ctx.modulesToObfuscate) + { + RemoveObfuzAttributes(mod); + foreach (TypeDef type in mod.GetTypes()) + { + RemoveObfuzAttributes(type); + foreach (FieldDef field in type.Fields) + { + RemoveObfuzAttributes(field); + } + foreach (MethodDef method in type.Methods) + { + RemoveObfuzAttributes(method); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + RemoveObfuzAttributes(param.ParamDef); + } + } + } + foreach (PropertyDef property in type.Properties) + { + RemoveObfuzAttributes(property); + } + foreach (EventDef eventDef in type.Events) + { + RemoveObfuzAttributes(eventDef); + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta new file mode 100644 index 00000000..ff6039ec --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/CleanUp/RemoveObfuzAttributesPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b475010a7656a0439ca8664a3d2dbc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta new file mode 100644 index 00000000..1fdfee0b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18104d0c3c665ea489e566eec67f2aea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs new file mode 100644 index 00000000..2b775b56 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs @@ -0,0 +1,490 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + + public class ConfigurableEncryptPolicy : EncryptPolicyBase + { + class ObfuscationRule : IRule + { + public bool? disableEncrypt; + public bool? encryptInt; + public bool? encryptLong; + public bool? encryptFloat; + public bool? encryptDouble; + public bool? encryptArray; + public bool? encryptString; + + public bool? encryptConstInLoop; + public bool? encryptStringInLoop; + + public bool? cacheConstInLoop; + public bool? cacheConstNotInLoop; + public bool? cacheStringInLoop; + public bool? cacheStringNotInLoop; + + public void InheritParent(ObfuscationRule parentRule) + { + if (disableEncrypt == null) + disableEncrypt = parentRule.disableEncrypt; + if (encryptInt == null) + encryptInt = parentRule.encryptInt; + if (encryptLong == null) + encryptLong = parentRule.encryptLong; + if (encryptFloat == null) + encryptFloat = parentRule.encryptFloat; + if (encryptDouble == null) + encryptDouble = parentRule.encryptDouble; + if (encryptArray == null) + encryptArray = parentRule.encryptArray; + if (encryptString == null) + encryptString = parentRule.encryptString; + + if (encryptConstInLoop == null) + encryptConstInLoop = parentRule.encryptConstInLoop; + if (encryptStringInLoop == null) + encryptStringInLoop = parentRule.encryptStringInLoop; + + if (cacheConstInLoop == null) + cacheConstInLoop = parentRule.cacheConstInLoop; + if (cacheConstNotInLoop == null) + cacheConstNotInLoop = parentRule.cacheConstNotInLoop; + if (cacheStringInLoop == null) + cacheStringInLoop = parentRule.cacheStringInLoop; + if (cacheStringNotInLoop == null) + cacheStringNotInLoop = parentRule.cacheStringNotInLoop; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + disableEncrypt = false, + encryptInt = true, + encryptLong = true, + encryptFloat = true, + encryptDouble = true, + encryptArray = true, + encryptString = true, + encryptConstInLoop = true, + encryptStringInLoop = true, + cacheConstInLoop = true, + cacheConstNotInLoop = false, + cacheStringInLoop = true, + cacheStringNotInLoop = true, + }; + + private ObfuscationRule _global; + + public HashSet notEncryptInts = new HashSet(); + public HashSet notEncryptLongs = new HashSet(); + public HashSet notEncryptStrings = new HashSet(); + public List> notEncryptIntRanges = new List>(); + public List> notEncryptLongRanges = new List>(); + public List> notEncryptFloatRanges = new List>(); + public List> notEncryptDoubleRanges = new List>(); + public List> notEncryptArrayLengthRanges = new List>(); + public List> notEncryptStringLengthRanges = new List>(); + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _assemblySpecs = new Dictionary(); + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableEncryptPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobalElement); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _xmlParser.InheritParentRules(_global); + } + + private void ParseGlobalElement(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + case "whitelist": ParseWhitelist(configFile, ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("disableEncrypt")) + { + rule.disableEncrypt = ConfigUtil.ParseBool(ele.GetAttribute("disableEncrypt")); + } + if (ele.HasAttribute("encryptInt")) + { + rule.encryptInt = ConfigUtil.ParseBool(ele.GetAttribute("encryptInt")); + } + if (ele.HasAttribute("encryptLong")) + { + rule.encryptLong = ConfigUtil.ParseBool(ele.GetAttribute("encryptLong")); + } + if (ele.HasAttribute("encryptFloat")) + { + rule.encryptFloat = ConfigUtil.ParseBool(ele.GetAttribute("encryptFloat")); + } + if (ele.HasAttribute("encryptDouble")) + { + rule.encryptDouble = ConfigUtil.ParseBool(ele.GetAttribute("encryptDouble")); + } + if (ele.HasAttribute("encryptBytes")) + { + rule.encryptArray = ConfigUtil.ParseBool(ele.GetAttribute("encryptArray")); + } + if (ele.HasAttribute("encryptString")) + { + rule.encryptString = ConfigUtil.ParseBool(ele.GetAttribute("encryptString")); + } + + if (ele.HasAttribute("encryptConstInLoop")) + { + rule.encryptConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptConstInLoop")); + } + if (ele.HasAttribute("encryptStringInLoop")) + { + rule.encryptStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("encryptStringInLoop")); + } + if (ele.HasAttribute("cacheConstInLoop")) + { + rule.cacheConstInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstInLoop")); + } + if (ele.HasAttribute("cacheConstNotInLoop")) + { + rule.cacheConstNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheConstNotInLoop")); + } + if (ele.HasAttribute("cacheStringInLoop")) + { + rule.cacheStringInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringInLoop")); + } + if (ele.HasAttribute("cacheStringNotInLoop")) + { + rule.cacheStringNotInLoop = ConfigUtil.ParseBool(ele.GetAttribute("cacheStringNotInLoop")); + } + return rule; + } + + private void ParseWhitelist(string configFile, XmlElement childEle) + { + string type = childEle.GetAttribute("type"); + if (string.IsNullOrEmpty(type)) + { + throw new Exception($"Invalid xml file, whitelist type is empty"); + } + string value = childEle.InnerText; + switch (type) + { + case "int": + { + notEncryptInts.AddRange(value.Split(',').Select(s => int.Parse(s.Trim()))); + break; + } + case "long": + { + notEncryptLongs.AddRange(value.Split(',').Select(s => long.Parse(s.Trim()))); + break; + } + case "string": + { + notEncryptStrings.AddRange(value.Split(',').Select(s => s.Trim())); + break; + } + case "int-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, int-range {value} is invalid"); + } + notEncryptIntRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); + break; + } + case "long-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, long-range {value} is invalid"); + } + notEncryptLongRanges.Add(new NumberRange(ConfigUtil.ParseNullableLong(parts[0]), ConfigUtil.ParseNullableLong(parts[1]))); + break; + } + case "float-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, float-range {value} is invalid"); + } + notEncryptFloatRanges.Add(new NumberRange(ConfigUtil.ParseNullableFloat(parts[0]), ConfigUtil.ParseNullableFloat(parts[1]))); + break; + } + case "double-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, double-range {value} is invalid"); + } + notEncryptDoubleRanges.Add(new NumberRange(ConfigUtil.ParseNullableDouble(parts[0]), ConfigUtil.ParseNullableDouble(parts[1]))); + break; + } + case "string-length-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, string-length-range {value} is invalid"); + } + notEncryptStringLengthRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); + break; + } + case "array-length-range": + { + var parts = value.Split(','); + if (parts.Length != 2) + { + throw new Exception($"Invalid xml file, array-length-range {value} is invalid"); + } + notEncryptArrayLengthRanges.Add(new NumberRange(ConfigUtil.ParseNullableInt(parts[0]), ConfigUtil.ParseNullableInt(parts[1]))); + break; + } + default: throw new Exception($"Invalid xml file, unknown whitelist type {type} in {childEle.Name} node"); + } + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscateMethod(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.disableEncrypt != true; + } + + public override ConstCachePolicy GetMethodConstCachePolicy(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return new ConstCachePolicy + { + cacheConstInLoop = rule.cacheConstInLoop.Value, + cacheConstNotInLoop = rule.cacheConstNotInLoop.Value, + cacheStringInLoop = rule.cacheStringInLoop.Value, + cacheStringNotInLoop = rule.cacheStringNotInLoop.Value, + }; + } + + public override bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptInt == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptInts.Contains(value)) + { + return false; + } + foreach (var range in notEncryptIntRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptLong == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptLongs.Contains(value)) + { + return false; + } + foreach (var range in notEncryptLongRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptFloat == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptFloatRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptDouble == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptDoubleRanges) + { + if (range.min != null && value < range.min) + { + continue; + } + if (range.max != null && value > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptString == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + if (notEncryptStrings.Contains(value)) + { + return false; + } + foreach (var range in notEncryptStringLengthRanges) + { + if (range.min != null && value.Length < range.min) + { + continue; + } + if (range.max != null && value.Length > range.max) + { + continue; + } + return false; + } + return true; + } + + public override bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + if (rule.encryptArray == false) + { + return false; + } + if (currentInLoop && rule.encryptConstInLoop == false) + { + return false; + } + foreach (var range in notEncryptArrayLengthRanges) + { + if (range.min != null && array.Length < range.min) + { + continue; + } + if (range.max != null && array.Length > range.max) + { + continue; + } + return false; + } + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta new file mode 100644 index 00000000..73c07be9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConfigurableEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da25453bc1fda394097c052af7733260 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs new file mode 100644 index 00000000..896e08a7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs @@ -0,0 +1,137 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Settings; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + + public class ConstEncryptPass : BasicBlockObfuscationPassBase + { + private readonly ConstEncryptionSettingsFacade _settings; + private IEncryptPolicy _dataObfuscatorPolicy; + private IConstEncryptor _dataObfuscator; + public override ObfuscationPassType Type => ObfuscationPassType.ConstEncrypt; + + public ConstEncryptPass(ConstEncryptionSettingsFacade settings) + { + _settings = settings; + } + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _dataObfuscatorPolicy = new ConfigurableEncryptPolicy(ctx.coreSettings.assembliesToObfuscate, _settings.ruleFiles); + _dataObfuscator = new DefaultConstEncryptor(ctx.moduleEntityManager, _settings); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _dataObfuscatorPolicy.NeedObfuscateMethod(method); + } + + protected override bool TryObfuscateInstruction(MethodDef method, Instruction inst, BasicBlock block, int instructionIndex, IList globalInstructions, + List outputInstructions, List totalFinalInstructions) + { + bool currentInLoop = block.inLoop; + ConstCachePolicy constCachePolicy = _dataObfuscatorPolicy.GetMethodConstCachePolicy(method); + bool needCache = currentInLoop ? constCachePolicy.cacheConstInLoop : constCachePolicy.cacheConstNotInLoop; + switch (inst.OpCode.Code) + { + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_M1: + { + int value = inst.GetLdcI4Value(); + if (_dataObfuscatorPolicy.NeedObfuscateInt(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateInt(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_I8: + { + long value = (long)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateLong(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateLong(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_R4: + { + float value = (float)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateFloat(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateFloat(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldc_R8: + { + double value = (double)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateDouble(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateDouble(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Ldstr: + { + string value = (string)inst.Operand; + if (_dataObfuscatorPolicy.NeedObfuscateString(method, currentInLoop, value)) + { + _dataObfuscator.ObfuscateString(method, needCache, value, outputInstructions); + return true; + } + return false; + } + case Code.Call: + { + if (((IMethod)inst.Operand).FullName == "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)") + { + Instruction prevInst = globalInstructions[instructionIndex - 1]; + if (prevInst.OpCode.Code == Code.Ldtoken) + { + IField rvaField = (IField)prevInst.Operand; + FieldDef ravFieldDef = rvaField.ResolveFieldDefThrow(); + if (ravFieldDef.Module != method.Module) + { + return false; + } + byte[] data = ravFieldDef.InitialValue; + if (data != null && data.Length > 0 && _dataObfuscatorPolicy.NeedObfuscateArray(method, currentInLoop, data)) + { + // don't need cache for byte array obfuscation + needCache = false; + _dataObfuscator.ObfuscateBytes(method, needCache, ravFieldDef, data, outputInstructions); + return true; + } + } + } + return false; + } + default: return false; + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta new file mode 100644 index 00000000..da7fdfbf --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/ConstEncryptPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa0e9191126d4e24c92546b6af2c52cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs new file mode 100644 index 00000000..24b3cb93 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs @@ -0,0 +1,337 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; +using System.Text; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public class DefaultConstEncryptor : IConstEncryptor + { + private readonly GroupByModuleEntityManager _moduleEntityManager; + private readonly ConstEncryptionSettingsFacade _settings; + + public DefaultConstEncryptor(GroupByModuleEntityManager moduleEntityManager, ConstEncryptionSettingsFacade settings) + { + _moduleEntityManager = moduleEntityManager; + _settings = settings; + } + + private IRandom CreateRandomForValue(EncryptionScopeInfo encryptionScope, int value) + { + return encryptionScope.localRandomCreator(value); + } + + private int GenerateEncryptionOperations(EncryptionScopeInfo encryptionScope, IRandom random) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, encryptionScope.encryptor, _settings.encryptionLevel); + } + + public int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private DefaultMetadataImporter GetModuleMetadataImporter(MethodDef method) + { + return _moduleEntityManager.GetEntity(method.Module); + } + + public void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + ConstFieldAllocator constFieldAllocator = _moduleEntityManager.GetEntity(method.Module); + RvaDataAllocator rvaDataAllocator = _moduleEntityManager.GetEntity(method.Module); + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + + switch (random.NextInt(5)) + { + case 0: + { + // = c = encrypted static field + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + break; + } + case 1: + { + // c = a + b + int a = random.NextInt(); + int b = value - a; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoInt(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Add)); + break; + } + case 2: + { + // c = a * b + int a = random.NextInt() | 0x1; + int ra = MathUtil.ModInverse32(a); + int b = ra * value; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoInt(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Mul)); + break; + } + case 3: + { + // c = a ^ b + int a = random.NextInt(); + int b = a ^ value; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoInt(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Xor)); + break; + } + default: + { + if (needCacheValue) + { + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + int encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaInt)); + break; + } + } + + + } + + public void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + ConstFieldAllocator constFieldAllocator = _moduleEntityManager.GetEntity(method.Module); + RvaDataAllocator rvaDataAllocator = _moduleEntityManager.GetEntity(method.Module); + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + + switch (random.NextInt(5)) + { + case 0: + { + // c = encrypted static field + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + break; + } + case 1: + { + // c = a + b + long a = random.NextLong(); + long b = value - a; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoLong(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Add)); + break; + } + case 2: + { + // c = a * b + long a = random.NextLong() | 0x1; + long ra = MathUtil.ModInverse64(a); + long b = ra * value; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoLong(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Mul)); + break; + } + case 3: + { + // c = a ^ b + long a = random.NextLong(); + long b = a ^ value; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstTwoLong(a, b, random, constProbability, constFieldAllocator, obfuscatedInstructions); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Xor)); + break; + } + default: + { + if (needCacheValue) + { + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + long encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaLong)); + break; + } + } + + + } + + public void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + ConstFieldAllocator constFieldAllocator = _moduleEntityManager.GetEntity(method.Module); + RvaDataAllocator rvaDataAllocator = _moduleEntityManager.GetEntity(method.Module); + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + + if (needCacheValue) + { + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + float encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaFloat)); + } + + public void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + ConstFieldAllocator constFieldAllocator = _moduleEntityManager.GetEntity(method.Module); + RvaDataAllocator rvaDataAllocator = _moduleEntityManager.GetEntity(method.Module); + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + + if (needCacheValue) + { + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + double encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaDouble)); + } + + + class EncryptedRvaDataInfo + { + public readonly FieldDef fieldDef; + public readonly byte[] originalBytes; + public readonly byte[] encryptedBytes; + public readonly int opts; + public readonly int salt; + + public EncryptedRvaDataInfo(FieldDef fieldDef, byte[] originalBytes, byte[] encryptedBytes, int opts, int salt) + { + this.fieldDef = fieldDef; + this.originalBytes = originalBytes; + this.encryptedBytes = encryptedBytes; + this.opts = opts; + this.salt = salt; + } + } + + private readonly Dictionary _encryptedRvaFields = new Dictionary(); + + private EncryptedRvaDataInfo GetEncryptedRvaData(FieldDef fieldDef) + { + if (!_encryptedRvaFields.TryGetValue(fieldDef, out var encryptedRvaData)) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(fieldDef.Module); + IRandom random = CreateRandomForValue(encryptionScope, FieldEqualityComparer.CompareDeclaringTypes.GetHashCode(fieldDef)); + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + byte[] originalBytes = fieldDef.InitialValue; + byte[] encryptedBytes = (byte[])originalBytes.Clone(); + encryptionScope.encryptor.EncryptBlock(encryptedBytes, ops, salt); + Assert.AreNotEqual(originalBytes, encryptedBytes, "Original bytes should not be the same as encrypted bytes."); + encryptedRvaData = new EncryptedRvaDataInfo(fieldDef, originalBytes, encryptedBytes, ops, salt); + _encryptedRvaFields.Add(fieldDef, encryptedRvaData); + fieldDef.InitialValue = encryptedBytes; + byte[] decryptedBytes = (byte[])encryptedBytes.Clone(); + encryptionScope.encryptor.DecryptBlock(decryptedBytes, ops, salt); + AssertUtil.AreArrayEqual(originalBytes, decryptedBytes, "Decrypted bytes should match the original bytes after encryption and decryption."); + } + return encryptedRvaData; + } + + + public void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions) + { + EncryptedRvaDataInfo encryptedData = GetEncryptedRvaData(field); + Assert.AreEqual(value.Length, encryptedData.encryptedBytes.Length); + + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.encryptedBytes.Length)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.opts)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(encryptedData.salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptInitializeArray)); + } + + public void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions) + { + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + IRandom random = CreateRandomForValue(encryptionScope, value.GetHashCode()); + ConstFieldAllocator constFieldAllocator = _moduleEntityManager.GetEntity(method.Module); + RvaDataAllocator rvaDataAllocator = _moduleEntityManager.GetEntity(method.Module); + DefaultMetadataImporter importer = GetModuleMetadataImporter(method); + + if (needCacheValue) + { + FieldDef cacheField = constFieldAllocator.Allocate(value); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, cacheField)); + return; + } + + int ops = GenerateEncryptionOperations(encryptionScope, random); + int salt = GenerateSalt(random); + int stringByteLength = Encoding.UTF8.GetByteCount(value); + byte[] encryptedValue = encryptionScope.encryptor.Encrypt(value, ops, salt); + Assert.AreEqual(stringByteLength, encryptedValue.Length); + RvaData rvaData = rvaDataAllocator.Allocate(encryptedValue); + + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Ldsfld, rvaData.field)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(rvaData.offset)); + // should use stringByteLength, can't use rvaData.size, because rvaData.size is align to 4, it's not the actual length. + obfuscatedInstructions.Add(Instruction.CreateLdcI4(stringByteLength)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(ops)); + obfuscatedInstructions.Add(Instruction.CreateLdcI4(salt)); + obfuscatedInstructions.Add(Instruction.Create(OpCodes.Call, importer.DecryptFromRvaString)); + } + + public void Done() + { + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta new file mode 100644 index 00000000..1e0977a2 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/DefaultConstEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c6a0ecde97527e4694731e4d4de129a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs new file mode 100644 index 00000000..0b2c087c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs @@ -0,0 +1,32 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public interface IConstEncryptor + { + void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions); + + void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions); + + void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions); + + void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions); + + void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); + + void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions); + } + + public abstract class ConstEncryptorBase : IConstEncryptor + { + public abstract void ObfuscateBytes(MethodDef method, bool needCacheValue, byte[] value, List obfuscatedInstructions); + public abstract void ObfuscateDouble(MethodDef method, bool needCacheValue, double value, List obfuscatedInstructions); + public abstract void ObfuscateFloat(MethodDef method, bool needCacheValue, float value, List obfuscatedInstructions); + public abstract void ObfuscateInt(MethodDef method, bool needCacheValue, int value, List obfuscatedInstructions); + public abstract void ObfuscateLong(MethodDef method, bool needCacheValue, long value, List obfuscatedInstructions); + public abstract void ObfuscateString(MethodDef method, bool needCacheValue, string value, List obfuscatedInstructions); + public abstract void ObfuscateBytes(MethodDef method, bool needCacheValue, FieldDef field, byte[] value, List obfuscatedInstructions); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta new file mode 100644 index 00000000..7f8d5871 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IConstEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ccbcdadf1913b6498eaee53abac5d0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs new file mode 100644 index 00000000..0067cc0f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs @@ -0,0 +1,43 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.ConstEncrypt +{ + public struct ConstCachePolicy + { + public bool cacheConstInLoop; + public bool cacheConstNotInLoop; + public bool cacheStringInLoop; + public bool cacheStringNotInLoop; + } + + public interface IEncryptPolicy + { + bool NeedObfuscateMethod(MethodDef method); + + ConstCachePolicy GetMethodConstCachePolicy(MethodDef method); + + bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value); + + bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value); + + bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value); + + bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value); + + bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value); + + bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array); + } + + public abstract class EncryptPolicyBase : IEncryptPolicy + { + public abstract bool NeedObfuscateMethod(MethodDef method); + public abstract ConstCachePolicy GetMethodConstCachePolicy(MethodDef method); + public abstract bool NeedObfuscateDouble(MethodDef method, bool currentInLoop, double value); + public abstract bool NeedObfuscateFloat(MethodDef method, bool currentInLoop, float value); + public abstract bool NeedObfuscateInt(MethodDef method, bool currentInLoop, int value); + public abstract bool NeedObfuscateLong(MethodDef method, bool currentInLoop, long value); + public abstract bool NeedObfuscateString(MethodDef method, bool currentInLoop, string value); + public abstract bool NeedObfuscateArray(MethodDef method, bool currentInLoop, byte[] array); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta new file mode 100644 index 00000000..0a44790d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ConstEncrypt/IEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18e57864070430a44ac561bdd7d00b2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus.meta new file mode 100644 index 00000000..9013ba54 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 02fb097cf61874c41923b3ef23fee199 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 00000000..b86b1e7c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,133 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.ControlFlowObfus +{ + struct ObfuscationRuleData + { + public readonly ObfuscationLevel obfuscationLevel; + public ObfuscationRuleData(ObfuscationLevel level) + { + obfuscationLevel = level; + } + } + + interface IObfuscationPolicy + { + bool NeedObfuscate(MethodDef method); + + ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscate(MethodDef method); + + public abstract ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + class ObfuscationRule : IRule + { + public ObfuscationLevel? obfuscationLevel; + + public void InheritParent(ObfuscationRule parentRule) + { + if (obfuscationLevel == null) + obfuscationLevel = parentRule.obfuscationLevel; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + obfuscationLevel = ObfuscationLevel.Basic, + }; + + private ObfuscationRule _global; + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobal); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _xmlParser.InheritParentRules(_global); + } + + private void ParseGlobal(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("obfuscationLevel")) + { + rule.obfuscationLevel = ConfigUtil.ParseObfuscationLevel(ele.GetAttribute("obfuscationLevel")); + } + return rule; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscate(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.obfuscationLevel.Value > ObfuscationLevel.None; + } + + public override ObfuscationRuleData GetObfuscationRuleData(MethodDef method) + { + var rule = GetMethodObfuscationRule(method); + return new ObfuscationRuleData(rule.obfuscationLevel.Value); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 00000000..1203f4b8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6983877d8859df4882c30f75be7a70e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs new file mode 100644 index 00000000..bcb04e05 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs @@ -0,0 +1,80 @@ +using dnlib.DotNet; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; + +namespace Obfuz.ObfusPasses.ControlFlowObfus +{ + class ObfusMethodContext + { + public MethodDef method; + public LocalVariableAllocator localVariableAllocator; + public IRandom localRandom; + public EncryptionScopeInfo encryptionScope; + public DefaultMetadataImporter importer; + public ConstFieldAllocator constFieldAllocator; + public int minInstructionCountOfBasicBlockToObfuscate; + + public IRandom CreateRandom() + { + return encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)); + } + } + + internal class ControlFlowObfusPass : ObfuscationMethodPassBase + { + private readonly ControlFlowObfuscationSettingsFacade _settings; + + private IObfuscationPolicy _obfuscationPolicy; + private IObfuscator _obfuscator; + + public ControlFlowObfusPass(ControlFlowObfuscationSettingsFacade settings) + { + _settings = settings; + _obfuscator = new DefaultObfuscator(); + } + + public override ObfuscationPassType Type => ObfuscationPassType.ControlFlowObfus; + + public override void Start() + { + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + _obfuscationPolicy = new ConfigurableObfuscationPolicy( + ctx.coreSettings.assembliesToObfuscate, + _settings.ruleFiles); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _obfuscationPolicy.NeedObfuscate(method); + } + + protected override void ObfuscateData(MethodDef method) + { + //Debug.Log($"Obfuscating method: {method.FullName} with EvalStackObfusPass"); + + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + GroupByModuleEntityManager moduleEntityManager = ctx.moduleEntityManager; + var encryptionScope = moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + var ruleData = _obfuscationPolicy.GetObfuscationRuleData(method); + var localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)); + var obfusMethodCtx = new ObfusMethodContext + { + method = method, + localVariableAllocator = new LocalVariableAllocator(method), + encryptionScope = encryptionScope, + constFieldAllocator = moduleEntityManager.GetEntity(method.Module), + localRandom = localRandom, + importer = moduleEntityManager.GetEntity(method.Module), + minInstructionCountOfBasicBlockToObfuscate = _settings.minInstructionCountOfBasicBlockToObfuscate, + }; + _obfuscator.Obfuscate(method, obfusMethodCtx); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs.meta new file mode 100644 index 00000000..ad62fd65 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/ControlFlowObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf62db4d3137e6447bd5cb2a65f101d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs new file mode 100644 index 00000000..9bd216aa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet; +using UnityEngine; + +namespace Obfuz.ObfusPasses.ControlFlowObfus +{ + class DefaultObfuscator : ObfuscatorBase + { + public override bool Obfuscate(MethodDef method, ObfusMethodContext ctx) + { + //Debug.Log($"Obfuscating method: {method.FullName} with ControlFlowObfusPass"); + var mcfc = new MethodControlFlowCalculator(method, ctx.CreateRandom(), ctx.constFieldAllocator, ctx.minInstructionCountOfBasicBlockToObfuscate); + if (!mcfc.TryObfus()) + { + //Debug.LogWarning($"not obfuscate method: {method.FullName}"); + return false; + } + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs.meta new file mode 100644 index 00000000..25926093 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/DefaultObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8aa2a2e43fa066541b982dbb63452458 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs new file mode 100644 index 00000000..7909bf2c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs @@ -0,0 +1,15 @@ +using dnlib.DotNet; +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses.ControlFlowObfus +{ + interface IObfuscator + { + bool Obfuscate(MethodDef method, ObfusMethodContext ctx); + } + + abstract class ObfuscatorBase : IObfuscator + { + public abstract bool Obfuscate(MethodDef method, ObfusMethodContext ctx); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs.meta new file mode 100644 index 00000000..e909f237 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ada5f6005768f745a18dc8b968e1684 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs new file mode 100644 index 00000000..06488c0d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs @@ -0,0 +1,894 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.ControlFlowObfus +{ + class MethodControlFlowCalculator + { + class BasicBlockInputOutputArguments + { + public readonly List locals = new List(); + + public BasicBlockInputOutputArguments() + { + } + + public BasicBlockInputOutputArguments(MethodDef method, List inputStackDatas) + { + ICorLibTypes corLibTypes = method.Module.CorLibTypes; + foreach (var data in inputStackDatas) + { + Local local = new Local(GetLocalTypeSig(corLibTypes, data)); + locals.Add(local); + method.Body.Variables.Add(local); + } + } + + private TypeSig GetLocalTypeSig(ICorLibTypes corLibTypes, EvalDataTypeWithSig type) + { + switch (type.type) + { + case EvalDataType.Int32: return corLibTypes.Int32; + case EvalDataType.Int64: return corLibTypes.Int64; + case EvalDataType.Float: return corLibTypes.Single; + case EvalDataType.Double: return corLibTypes.Double; + case EvalDataType.I: return corLibTypes.IntPtr; + case EvalDataType.Ref: Assert.IsNotNull(type.typeSig); return type.typeSig; + case EvalDataType.ValueType: Assert.IsNotNull(type.typeSig); return type.typeSig; + case EvalDataType.Token: throw new System.NotSupportedException("Token type is not supported in BasicBlockInputOutputArguments"); + default: throw new System.NotSupportedException("not supported EvalDataType"); + } + } + } + + class BasicBlockInfo + { + public BlockGroup group; + + //public int order; + public bool isSaveStackBlock; + public BasicBlockInfo prev; + public BasicBlockInfo next; + + public List instructions; + public List inputStackDatas; + public List outputStackDatas; + + public List inBasicBlocks = new List(); + public List outBasicBlocks = new List(); + + public BasicBlockInputOutputArguments inputArgs; + public BasicBlockInputOutputArguments outputArgs; + + public Instruction FirstInstruction => instructions[0]; + + public Instruction LastInstruction => instructions[instructions.Count - 1]; + + public Instruction GroupFirstInstruction => group.basicBlocks[0].FirstInstruction; + + + //public void InsertNext(BasicBlockInfo nextBb) + //{ + // if (next != null) + // { + // next.prev = nextBb; + // nextBb.next = next; + // } + // nextBb.prev = this; + // next = nextBb; + //} + + public void InsertBefore(BasicBlockInfo prevBb) + { + prev.next = prevBb; + prevBb.prev = prev; + prevBb.next = this; + this.prev = prevBb; + } + + public void AddOutBasicBlock(BasicBlockInfo outBb) + { + if (!outBasicBlocks.Contains(outBb)) + { + outBasicBlocks.Add(outBb); + outBb.inBasicBlocks.Add(this); + } + } + + public void ClearInBasicBlocks() + { + foreach (var inBb in inBasicBlocks) + { + inBb.outBasicBlocks.Remove(this); + } + inBasicBlocks.Clear(); + } + + public void RetargetInBasicBlocksTo(BasicBlockInfo prevBb, Dictionary inst2bb) + { + var oldInBlocks = new List(inBasicBlocks); + ClearInBasicBlocks(); + foreach (var oldInBb in oldInBlocks) + { + oldInBb.AddOutBasicBlock(prevBb); + } + // inBB => saveBb => cur + foreach (BasicBlockInfo inBb in prevBb.inBasicBlocks) + { + if (inBb.instructions.Count == 0) + { + // empty block, no need to retarget + continue; + } + Instruction lastInst = inBb.instructions.Last(); + if (lastInst.Operand is Instruction targetInst) + { + if (inst2bb.TryGetValue(targetInst, out BasicBlockInfo targetBb) && targetBb == this) + { + // retarget to prevBb + lastInst.Operand = prevBb.FirstInstruction; + } + } + else if (lastInst.Operand is Instruction[] targetInsts) + { + for (int i = 0; i < targetInsts.Length; i++) + { + targetInst = targetInsts[i]; + if (inst2bb.TryGetValue(targetInst, out BasicBlockInfo targetBb) && targetBb == this) + { + targetInsts[i] = prevBb.FirstInstruction; + } + } + } + } + } + } + + private readonly MethodDef _method; + private readonly IRandom _random; + private readonly ConstFieldAllocator _constFieldAllocator; + private readonly int _minInstructionCountOfBasicBlockToObfuscate; + private readonly BasicBlockInfo _bbHead; + + public MethodControlFlowCalculator(MethodDef method, IRandom random, ConstFieldAllocator constFieldAllocator, int minInstructionCountOfBasicBlockToObfuscate) + { + _method = method; + _random = random; + _constFieldAllocator = constFieldAllocator; + _minInstructionCountOfBasicBlockToObfuscate = minInstructionCountOfBasicBlockToObfuscate; + + _bbHead = new BasicBlockInfo() + { + instructions = new List(), + inputStackDatas = new List(), + outputStackDatas = new List(), + }; + } + + private void BuildBasicBlockLink(EvalStackCalculator evc) + { + BasicBlockInfo prev = _bbHead; + var bb2bb = new Dictionary(); + foreach (BasicBlock bb in evc.BasicBlockCollection.Blocks) + { + EvalStackState ess = evc.GetEvalStackState(bb); + var newBB = new BasicBlockInfo + { + prev = prev, + next = null, + instructions = bb.instructions, + inputStackDatas = ess.inputStackDatas, + outputStackDatas = ess.runStackDatas, + }; + prev.next = newBB; + prev = newBB; + bb2bb.Add(bb, newBB); + } + foreach (BasicBlock bb in evc.BasicBlockCollection.Blocks) + { + BasicBlockInfo bbi = bb2bb[bb]; + foreach (var inBb in bb.inBlocks) + { + bbi.inBasicBlocks.Add(bb2bb[inBb]); + } + foreach (var outBb in bb.outBlocks) + { + bbi.outBasicBlocks.Add(bb2bb[outBb]); + } + } + + // let _bbHead point to the first basic block + //_bbHead.instructions.Add(Instruction.Create(OpCodes.Br, _bbHead.next.FirstInstruction)); + _bbHead.next.inBasicBlocks.Add(_bbHead); + _bbHead.outBasicBlocks.Add(_bbHead.next); + } + + private bool CheckNotContainsNotSupportedEvalStackData() + { + for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next) + { + foreach (var data in cur.inputStackDatas) + { + if (data.type == EvalDataType.Unknown || data.type == EvalDataType.Token) + { + Debug.LogError($"NotSupported EvalStackData found in method: {_method.FullName}, type: {data.type}"); + return false; + } + } + } + return true; + } + + + private void WalkInputArgumentGroup(BasicBlockInfo cur, BasicBlockInputOutputArguments inputArgs) + { + if (cur.inputArgs != null) + { + Assert.AreEqual(cur.inputArgs, inputArgs, "input arguments not match"); + return; + } + cur.inputArgs = inputArgs; + foreach (BasicBlockInfo inputBB in cur.inBasicBlocks) + { + if (inputBB.outputArgs != null) + { + Assert.AreEqual(inputBB.outputArgs, inputArgs, $"Input BB {inputBB} outputArgs does not match in method: {_method.FullName}"); + continue; + } + inputBB.outputArgs = cur.inputArgs; + foreach (var outBB in inputBB.outBasicBlocks) + { + WalkInputArgumentGroup(outBB, inputArgs); + } + } + } + + private readonly BasicBlockInputOutputArguments emptyEvalStackArgs = new BasicBlockInputOutputArguments(); + + private void ComputeInputOutputArguments() + { + for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next) + { + if (cur.inputArgs == null) + { + if (cur.inputStackDatas.Count == 0) + { + cur.inputArgs = emptyEvalStackArgs; + } + else + { + var inputArgs = new BasicBlockInputOutputArguments(_method, cur.inputStackDatas); + WalkInputArgumentGroup(cur, inputArgs); + } + } + if (cur.outputArgs == null && cur.outputStackDatas.Count == 0) + { + cur.outputArgs = emptyEvalStackArgs; + } + } + for (BasicBlockInfo cur = _bbHead; cur != null; cur = cur.next) + { + if (cur.inputArgs == null) + { + throw new System.Exception($"Input arguments for BasicBlock {cur} in method {_method.FullName} is null"); + } + if (cur.outputArgs == null) + { + if (cur.instructions.Count > 0) + { + Code lastInstCode = cur.LastInstruction.OpCode.Code; + Assert.IsTrue(lastInstCode == Code.Throw || lastInstCode == Code.Rethrow); + cur.outputStackDatas = new List(); + } + cur.outputArgs = emptyEvalStackArgs; + } + } + } + + + private BasicBlockInfo CreateSaveStackBasicBlock(BasicBlockInfo to) + { + if (to.group == null) + { + throw new Exception($"BasicBlock {to} in method {_method.FullName} does not belong to any group. This should not happen."); + } + + var saveLocalBasicBlock = new BasicBlockInfo + { + group = to.group, + isSaveStackBlock = true, + inputStackDatas = to.inputStackDatas, + inputArgs = to.inputArgs, + outputStackDatas = new List(), + outputArgs = emptyEvalStackArgs, + instructions = new List(), + }; + + var locals = to.inputArgs.locals; + if (locals.Count > 0) + { + to.instructions.InsertRange(0, locals.Select(l => Instruction.Create(OpCodes.Ldloc, l))); + + } + for (int i = locals.Count - 1; i >= 0; i--) + { + saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Stloc, locals[i])); + } + + to.inputArgs = emptyEvalStackArgs; + to.inputStackDatas = new List(); + + BlockGroup group = to.group; + group.basicBlocks.Insert(group.basicBlocks.IndexOf(to), saveLocalBasicBlock); + group.switchMachineCases.Add(new SwitchMachineCase { index = -1, prepareBlock = saveLocalBasicBlock, targetBlock = to}); + saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Ldsfld, (FieldDef)null)); + saveLocalBasicBlock.instructions.Add(Instruction.Create(OpCodes.Br, group.switchMachineInst)); + + + return saveLocalBasicBlock; + } + + private void AdjustInputOutputEvalStack() + { + Dictionary inst2bb = BuildInstructionToBasicBlockInfoDic(); + for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next) + { + if (cur.inputArgs.locals.Count == 0 && cur.instructions.Count < _minInstructionCountOfBasicBlockToObfuscate) + { + // small block, no need to save stack + continue; + } + + BasicBlockInfo saveBb = CreateSaveStackBasicBlock(cur); + cur.InsertBefore(saveBb); + cur.RetargetInBasicBlocksTo(saveBb, inst2bb); + //saveBb.AddOutBasicBlock(cur); + } + } + + private void InsertSwitchMachineBasicBlockForGroups(BlockGroup rootGroup) + { + Dictionary inst2bb = BuildInstructionToBasicBlockInfoDic(); + + InsertSwitchMachineBasicBlockForGroup(rootGroup, inst2bb); + } + + //private void ShuffleBasicBlocks0(List bbs) + //{ + // int n = bbs.Count; + // if (n <= 1) + // { + // return; + // } + + // var firstSection = new List() { bbs[0] }; + // var sectionsExcludeFirstLast = new List>(); + // List currentSection = firstSection; + // for (int i = 1; i < n; i++) + // { + // BasicBlockInfo cur = bbs[i]; + // if (cur.inputArgs.locals.Count == 0) + // { + // currentSection = new List() { cur }; + // sectionsExcludeFirstLast.Add(currentSection); + // } + // else + // { + // currentSection.Add(cur); + // } + // } + // if (sectionsExcludeFirstLast.Count <= 1) + // { + // return; + // } + // var lastSection = sectionsExcludeFirstLast.Last(); + // sectionsExcludeFirstLast.RemoveAt(sectionsExcludeFirstLast.Count - 1); + + + // RandomUtil.ShuffleList(sectionsExcludeFirstLast, _random); + + // bbs.Clear(); + // bbs.AddRange(firstSection); + // bbs.AddRange(sectionsExcludeFirstLast.SelectMany(section => section)); + // bbs.AddRange(lastSection); + // Assert.AreEqual(n, bbs.Count, "Shuffled basic blocks count should be the same as original count"); + //} + + private void ShuffleBasicBlocks(List bbs) + { + // TODO + + //int n = bbs.Count; + //BasicBlockInfo groupPrev = bbs[0].prev; + //BasicBlockInfo groupNext = bbs[n - 1].next; + ////RandomUtil.ShuffleList(bbs, _random); + //ShuffleBasicBlocks0(bbs); + //BasicBlockInfo prev = groupPrev; + //for (int i = 0; i < n; i++) + //{ + // BasicBlockInfo cur = bbs[i]; + // cur.prev = prev; + // prev.next = cur; + // prev = cur; + //} + //prev.next = groupNext; + //if (groupNext != null) + //{ + // groupNext.prev = prev; + //} + } + + private void InsertSwitchMachineBasicBlockForGroup(BlockGroup group, Dictionary inst2bb) + { + if (group.subGroups != null && group.subGroups.Count > 0) + { + foreach (var subGroup in group.subGroups) + { + InsertSwitchMachineBasicBlockForGroup(subGroup, inst2bb); + } + } + else if (group.switchMachineCases.Count > 0) + { + Assert.IsTrue(group.basicBlocks.Count > 0, "Group should contain at least one basic block"); + + BasicBlockInfo firstBlock = group.basicBlocks[0]; + var firstCase = group.switchMachineCases[0]; + //Assert.AreEqual(firstCase.prepareBlock, firstBlock, "First case prepare block should be the first basic block in group"); + + Assert.IsTrue(firstCase.targetBlock.inputArgs.locals.Count == 0); + Assert.IsTrue(firstCase.targetBlock.inputStackDatas.Count == 0); + + var instructions = new List() + { + Instruction.Create(OpCodes.Ldsfld, (FieldDef)null), + group.switchMachineInst, + Instruction.Create(OpCodes.Br, firstCase.targetBlock.FirstInstruction), + }; + if (firstCase.prepareBlock != firstBlock || firstBlock.inputStackDatas.Count != 0) + { + instructions.Insert(0, Instruction.Create(OpCodes.Br, firstBlock.FirstInstruction)); + } + + var switchMachineBb = new BasicBlockInfo() + { + group = group, + inputArgs = firstBlock.inputArgs, + outputArgs = emptyEvalStackArgs, + inputStackDatas = firstBlock.inputStackDatas, + outputStackDatas = new List(), + instructions = instructions, + }; + firstBlock.InsertBefore(switchMachineBb); + group.basicBlocks.Insert(0, switchMachineBb); + ShuffleBasicBlocks(group.basicBlocks); + + List switchTargets = (List)group.switchMachineInst.Operand; + + RandomUtil.ShuffleList(group.switchMachineCases, _random); + + for (int i = 0, n = group.switchMachineCases.Count; i < n; i++) + { + SwitchMachineCase switchMachineCase = group.switchMachineCases[i]; + switchMachineCase.index = i; + List prepareBlockInstructions = switchMachineCase.prepareBlock.instructions; + + Instruction setBranchIndexInst = prepareBlockInstructions[prepareBlockInstructions.Count - 2]; + Assert.AreEqual(setBranchIndexInst.OpCode, OpCodes.Ldsfld, "first instruction of prepareBlock should be Ldsfld"); + //setBranchIndexInst.Operand = i; + var indexField = _constFieldAllocator.Allocate(i); + setBranchIndexInst.Operand = indexField; + switchTargets.Add(switchMachineCase.targetBlock.FirstInstruction); + } + + // after shuffle + Assert.IsTrue(instructions.Count == 3 || instructions.Count == 4, "Switch machine basic block should contain 3 or 4 instructions"); + Assert.AreEqual(Code.Ldsfld, instructions[instructions.Count - 3].OpCode.Code, "First instruction should be Ldsfld"); + instructions[instructions.Count - 3].Operand = _constFieldAllocator.Allocate(firstCase.index); + } + } + + private bool IsPrevBasicBlockControlFlowNextToThis(BasicBlockInfo cur) + { + Instruction lastInst = cur.prev.LastInstruction; + switch (lastInst.OpCode.FlowControl) + { + case FlowControl.Cond_Branch: + case FlowControl.Call: + case FlowControl.Next: + case FlowControl.Break: + { + return true; + } + default: return false; + } + } + + private void InsertBrInstructionForConjoinedBasicBlocks() + { + for (BasicBlockInfo cur = _bbHead.next.next; cur != null; cur = cur.next) + { + if (cur.group == cur.prev.group && IsPrevBasicBlockControlFlowNextToThis(cur)) + { + cur.prev.instructions.Add(Instruction.Create(OpCodes.Br, cur.FirstInstruction)); + } + } + } + + private Dictionary BuildInstructionToBasicBlockInfoDic() + { + var inst2bb = new Dictionary(); + for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next) + { + foreach (var inst in cur.instructions) + { + inst2bb[inst] = cur; + } + } + return inst2bb; + } + + + private class SwitchMachineCase + { + public int index; + public BasicBlockInfo prepareBlock; + public BasicBlockInfo targetBlock; + } + + private class BlockGroup + { + public BlockGroup parent; + + public List instructions; + + public List subGroups; + + public List basicBlocks; + + public Instruction switchMachineInst; + public List switchMachineCases; + + public BlockGroup(List instructions, Dictionary inst2group) + { + this.instructions = instructions; + UpdateInstructionGroup(inst2group); + } + + public BlockGroup(BlockGroup parent, List instructions, Dictionary inst2group) + { + this.instructions = instructions; + UpdateInstructionGroup(parent, inst2group); + } + + public BlockGroup RootParent => parent == null ? this : parent.RootParent; + + public void SetParent(BlockGroup newParent) + { + if (parent != null) + { + Assert.IsTrue(parent != newParent, "Parent group should not be the same as new parent"); + Assert.IsTrue(parent.subGroups.Contains(this), "Parent group should already contain this group"); + parent.subGroups.Remove(this); + } + parent = newParent; + if (newParent.subGroups == null) + { + newParent.subGroups = new List(); + } + Assert.IsFalse(newParent.subGroups.Contains(this), "New parent group should not already contain this group"); + newParent.subGroups.Add(this); + } + + private void UpdateInstructionGroup(Dictionary inst2group) + { + foreach (var inst in instructions) + { + if (inst2group.TryGetValue(inst, out BlockGroup existGroup)) + { + if (this != existGroup) + { + BlockGroup rootParent = existGroup.RootParent; + if (rootParent != this) + { + rootParent.SetParent(this); + } + } + } + else + { + inst2group[inst] = this; + } + } + } + + private void UpdateInstructionGroup(BlockGroup parentGroup, Dictionary inst2group) + { + foreach (var inst in instructions) + { + BlockGroup existGroup = inst2group[inst]; + Assert.AreEqual(parentGroup, existGroup, "Instruction group parent should be the same as parent group"); + inst2group[inst] = this; + } + SetParent(parentGroup); + } + + public void SplitInstructionsNotInAnySubGroupsToIndividualGroups(Dictionary inst2group) + { + if (subGroups == null || subGroups.Count == 0 || instructions.Count == 0) + { + return; + } + + foreach (var subGroup in subGroups) + { + subGroup.SplitInstructionsNotInAnySubGroupsToIndividualGroups(inst2group); + } + + var finalGroupList = new List(); + var curGroupInstructions = new List(); + + var firstInst2SubGroup = subGroups.ToDictionary(g => g.instructions[0]); + foreach (var inst in instructions) + { + BlockGroup group = inst2group[inst]; + if (group == this) + { + curGroupInstructions.Add(inst); + } + else + { + if (curGroupInstructions.Count > 0) + { + finalGroupList.Add(new BlockGroup(this, curGroupInstructions, inst2group)); + curGroupInstructions = new List(); + } + if (firstInst2SubGroup.TryGetValue(inst, out var subGroup)) + { + finalGroupList.Add(subGroup); + } + } + } + if (curGroupInstructions.Count > 0) + { + finalGroupList.Add(new BlockGroup(this, curGroupInstructions, inst2group)); + } + this.subGroups = finalGroupList; + } + + public void ComputeBasicBlocks(Dictionary inst2bb) + { + if (subGroups == null || subGroups.Count == 0) + { + basicBlocks = new List(); + foreach (var inst in instructions) + { + BasicBlockInfo block = inst2bb[inst]; + if (block.group != null) + { + if (block.group != this) + { + throw new Exception("BasicBlockInfo group should be the same as this BlockGroup"); + } + } + else + { + block.group = this; + basicBlocks.Add(block); + } + } + switchMachineInst = Instruction.Create(OpCodes.Switch, new List()); + switchMachineCases = new List(); + return; + } + foreach (var subGroup in subGroups) + { + subGroup.ComputeBasicBlocks(inst2bb); + } + } + } + + private class TryBlockGroup : BlockGroup + { + public TryBlockGroup(List instructions, Dictionary inst2group) : base(instructions, inst2group) + { + } + } + + private class ExceptionHandlerGroup : BlockGroup + { + public readonly ExceptionHandler exceptionHandler; + + public ExceptionHandlerGroup(ExceptionHandler exceptionHandler, List instructions, Dictionary inst2group) : base(instructions, inst2group) + { + this.exceptionHandler = exceptionHandler; + } + } + + private class ExceptionFilterGroup : BlockGroup + { + public readonly ExceptionHandler exceptionHandler; + + public ExceptionFilterGroup(ExceptionHandler exceptionHandler, List instructions, Dictionary inst2group) : base(instructions, inst2group) + { + this.exceptionHandler = exceptionHandler; + } + } + + private class ExceptionHandlerWithFilterGroup : BlockGroup + { + public readonly ExceptionHandler exceptionHandler; + //public readonly ExceptionFilterGroup filterGroup; + //public readonly ExceptionHandlerGroup handlerGroup; + public ExceptionHandlerWithFilterGroup(ExceptionHandler exceptionHandler, List filterInstructions, List handlerInstructions, List allInstructions, Dictionary inst2group) : base(allInstructions, inst2group) + { + this.exceptionHandler = exceptionHandler; + var filterGroup = new ExceptionFilterGroup(exceptionHandler, filterInstructions, inst2group); + var handlerGroup = new ExceptionHandlerGroup(exceptionHandler, handlerInstructions, inst2group); + } + } + + class TryBlockInfo + { + public Instruction tryStart; + public Instruction tryEnd; + public TryBlockGroup blockGroup; + } + + private Dictionary BuildInstruction2Index() + { + IList instructions = _method.Body.Instructions; + var inst2Index = new Dictionary(instructions.Count); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + inst2Index.Add(inst, i); + } + return inst2Index; + } + + private BlockGroup SplitBasicBlockGroup() + { + Dictionary inst2Index = BuildInstruction2Index(); + var inst2blockGroup = new Dictionary(); + + List instructions = (List)_method.Body.Instructions; + + var tryBlocks = new List(); + foreach (var ex in _method.Body.ExceptionHandlers) + { + TryBlockInfo tryBlock = tryBlocks.Find(tryBlocks => tryBlocks.tryStart == ex.TryStart && tryBlocks.tryEnd == ex.TryEnd); + if (tryBlock == null) + { + int startIndex = inst2Index[ex.TryStart]; + int endIndex = ex.TryEnd != null ? inst2Index[ex.TryEnd] : inst2Index.Count; + TryBlockGroup blockGroup = new TryBlockGroup(instructions.GetRange(startIndex, endIndex - startIndex), inst2blockGroup); + tryBlock = new TryBlockInfo + { + tryStart = ex.TryStart, + tryEnd = ex.TryEnd, + blockGroup = blockGroup, + }; + tryBlocks.Add(tryBlock); + } + if (ex.FilterStart != null) + { + int filterStartIndex = inst2Index[ex.FilterStart]; + int filterEndIndex = ex.HandlerStart != null ? inst2Index[ex.HandlerStart] : inst2Index.Count; + int handlerStartIndex = filterEndIndex; + int handlerEndIndex = ex.HandlerEnd != null ? inst2Index[ex.HandlerEnd] : inst2Index.Count; + var filterHandlerGroup = new ExceptionHandlerWithFilterGroup(ex, + instructions.GetRange(filterStartIndex, filterEndIndex - filterStartIndex), + instructions.GetRange(handlerStartIndex, handlerEndIndex - handlerStartIndex), + instructions.GetRange(filterStartIndex, handlerEndIndex - filterStartIndex), inst2blockGroup); + } + else + { + int handlerStartIndex = inst2Index[ex.HandlerStart]; + int handlerEndIndex = ex.HandlerEnd != null ? inst2Index[ex.HandlerEnd] : inst2Index.Count; + ExceptionHandlerGroup handlerGroup = new ExceptionHandlerGroup(ex, instructions.GetRange(handlerStartIndex, handlerEndIndex - handlerStartIndex), inst2blockGroup); + } + } + var rootGroup = new BlockGroup(new List(instructions), inst2blockGroup); + rootGroup.SplitInstructionsNotInAnySubGroupsToIndividualGroups(inst2blockGroup); + + rootGroup.ComputeBasicBlocks(BuildInstructionToBasicBlockInfoDic()); + return rootGroup; + } + + private void FixInstructionTargets() + { + var inst2bb = BuildInstructionToBasicBlockInfoDic(); + foreach (var ex in _method.Body.ExceptionHandlers) + { + if (ex.TryStart != null) + { + ex.TryStart = inst2bb[ex.TryStart].GroupFirstInstruction; + } + if (ex.TryEnd != null) + { + ex.TryEnd = inst2bb[ex.TryEnd].GroupFirstInstruction; + } + if (ex.HandlerStart != null) + { + ex.HandlerStart = inst2bb[ex.HandlerStart].GroupFirstInstruction; + } + if (ex.HandlerEnd != null) + { + ex.HandlerEnd = inst2bb[ex.HandlerEnd].GroupFirstInstruction; + } + if (ex.FilterStart != null) + { + ex.FilterStart = inst2bb[ex.FilterStart].GroupFirstInstruction; + } + } + //foreach (var inst in inst2bb.Keys) + //{ + // if (inst.Operand is Instruction targetInst) + // { + // inst.Operand = inst2bb[targetInst].FirstInstruction; + // } + // else if (inst.Operand is Instruction[] targetInsts) + // { + // for (int i = 0; i < targetInsts.Length; i++) + // { + // targetInsts[i] = inst2bb[targetInsts[i]].FirstInstruction; + // } + // } + //} + } + + private void BuildInstructions() + { + IList methodInstructions = _method.Body.Instructions; + methodInstructions.Clear(); + for (BasicBlockInfo cur = _bbHead.next; cur != null; cur = cur.next) + { + foreach (Instruction inst in cur.instructions) + { + methodInstructions.Add(inst); + } + } + _method.Body.InitLocals = true; + //_method.Body.MaxStack = Math.Max(_method.Body.MaxStack , (ushort)1); // TODO: set to a reasonable value + //_method.Body.KeepOldMaxStack = true; + //_method.Body.UpdateInstructionOffsets(); + } + + public bool TryObfus() + { + // TODO: TEMP + //if (_method.Body.HasExceptionHandlers) + //{ + // return false; + //} + var evc = new EvalStackCalculator(_method); + BuildBasicBlockLink(evc); + if (!CheckNotContainsNotSupportedEvalStackData()) + { + Debug.LogError($"Method {_method.FullName} contains unsupported EvalStackData, obfuscation skipped."); + return false; + } + BlockGroup rootGroup = SplitBasicBlockGroup(); + if (rootGroup.basicBlocks != null && rootGroup.basicBlocks.Count == 1) + { + return false; + } + ComputeInputOutputArguments(); + AdjustInputOutputEvalStack(); + InsertBrInstructionForConjoinedBasicBlocks(); + InsertSwitchMachineBasicBlockForGroups(rootGroup); + + FixInstructionTargets(); + BuildInstructions(); + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs.meta new file mode 100644 index 00000000..059febf0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ControlFlowObfus/MethodControlFlowCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 144b6474de40382498899f8b1c7f92a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus.meta new file mode 100644 index 00000000..a52d43d3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e82ef0b94e10314cbba0daabfdefe32 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 00000000..2284324f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,147 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.EvalStackObfus +{ + struct ObfuscationRuleData + { + public readonly ObfuscationLevel obfuscationLevel; + public readonly float obfuscationPercentage; + public ObfuscationRuleData(ObfuscationLevel level, float percentage) + { + obfuscationLevel = level; + obfuscationPercentage = percentage; + } + } + + interface IObfuscationPolicy + { + bool NeedObfuscate(MethodDef method); + + ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscate(MethodDef method); + + public abstract ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + class ObfuscationRule : IRule + { + public ObfuscationLevel? obfuscationLevel; + public float? obfuscationPercentage; + + public void InheritParent(ObfuscationRule parentRule) + { + if (obfuscationLevel == null) + obfuscationLevel = parentRule.obfuscationLevel; + if (obfuscationPercentage == null) + obfuscationPercentage = parentRule.obfuscationPercentage; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + obfuscationLevel = ObfuscationLevel.Basic, + obfuscationPercentage = 0.05f, + }; + + private ObfuscationRule _global; + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobal); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + if (_global.obfuscationPercentage.Value > 0.1f) + { + UnityEngine.Debug.LogWarning($"EvalStackObfus significantly increases the size of the obfuscated hot-update DLL. It is recommended to keep the obfuscationPercentage ≤ 0.1 (currently set to {_global.obfuscationPercentage.Value})."); + } + _xmlParser.InheritParentRules(_global); + } + + private void ParseGlobal(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("obfuscationLevel")) + { + rule.obfuscationLevel = ConfigUtil.ParseObfuscationLevel(ele.GetAttribute("obfuscationLevel")); + } + if (ele.HasAttribute("obfuscationPercentage")) + { + rule.obfuscationPercentage = float.Parse(ele.GetAttribute("obfuscationPercentage")); + } + return rule; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscate(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.obfuscationLevel.Value > ObfuscationLevel.None; + } + + public override ObfuscationRuleData GetObfuscationRuleData(MethodDef method) + { + var rule = GetMethodObfuscationRule(method); + return new ObfuscationRuleData(rule.obfuscationLevel.Value, rule.obfuscationPercentage.Value); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 00000000..e9520c4e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a2603d51f31a134d90599d33664f6c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs new file mode 100644 index 00000000..d9b91838 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs @@ -0,0 +1,225 @@ +using dnlib.DotNet.Emit; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.EvalStackObfus +{ + class DefaultObfuscator : ObfuscatorBase + { + public override bool ObfuscateInt(Instruction inst, List outputInsts, ObfusMethodContext ctx) + { + IRandom random = ctx.localRandom; + switch (random.NextInt(4)) + { + case 0: + { + // x = x + a + int a = 0; + float constProbability = 0f; + ConstObfusUtil.LoadConstInt(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 1: + { + // x = x * a * ra + int a = random.NextInt() | 0x1; // Ensure a is not zero + int ra = MathUtil.ModInverse32(a); + float constProbability = 0.5f; + ConstObfusUtil.LoadConstInt(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + case 2: + { + // x = (x * a + b) * ra - (b * ra) + int a = random.NextInt() | 0x1; // Ensure a is not zero + int ra = MathUtil.ModInverse32(a); + int b = random.NextInt(); + int b_ra = -b * ra; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstInt(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstInt(ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(b_ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 3: + { + // x = ((x + a) * b + c) * rb - (a*b + c) * rb + int a = random.NextInt(); + int b = random.NextInt() | 0x1; // Ensure b is not zero + int rb = MathUtil.ModInverse32(b); + int c = random.NextInt(); + int r = -(a * b + c) * rb; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstInt(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstInt(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(c, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstInt(rb, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(r, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + default: return false; + } + } + + public override bool ObfuscateLong(Instruction inst, List outputInsts, ObfusMethodContext ctx) + { + IRandom random = ctx.localRandom; + switch (random.NextInt(4)) + { + case 0: + { + // x = x + a + long a = 0; + float constProbability = 0f; + ConstObfusUtil.LoadConstLong(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 1: + { + // x = x * a * ra + long a = random.NextLong() | 0x1L; // Ensure a is not zero + long ra = MathUtil.ModInverse64(a); + float constProbability = 0.5f; + ConstObfusUtil.LoadConstLong(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + case 2: + { + // x = (x * a + b) * ra - (b * ra) + long a = random.NextLong() | 0x1L; // Ensure a is not zero + long ra = MathUtil.ModInverse64(a); + long b = random.NextLong(); + long b_ra = -b * ra; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstLong(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstLong(ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(b_ra, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 3: + { + // x = ((x + a) * b + c) * rb - (a*b + c) * rb + long a = random.NextLong(); + long b = random.NextLong() | 0x1L; // Ensure b is not zero + long rb = MathUtil.ModInverse64(b); + long c = random.NextLong(); + long r = -(a * b + c) * rb; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstLong(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstLong(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(c, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstLong(rb, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(r, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + default: return false; + } + } + + public override bool ObfuscateFloat(Instruction inst, List outputInsts, ObfusMethodContext ctx) + { + IRandom random = ctx.localRandom; + switch (random.NextInt(3)) + { + case 0: + { + // x = x + 0f + float a = 0.0f; + float constProbability = 0f; + ConstObfusUtil.LoadConstFloat(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 1: + { + // x = x * 1f; + float a = 1.0f; + float constProbability = 0f; + ConstObfusUtil.LoadConstFloat(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + case 2: + { + // x = (x + a) * b; a = 0.0f, b = 1.0f + float a = 0.0f; + float b = 1.0f; + float constProbability = 0f; + ConstObfusUtil.LoadConstFloat(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstFloat(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + default: return false; + } + } + + public override bool ObfuscateDouble(Instruction inst, List outputInsts, ObfusMethodContext ctx) + { + IRandom random = ctx.localRandom; + switch (random.NextInt(3)) + { + case 0: + { + // x = x + 0.0 + double a = 0.0; + float constProbability = 0f; + ConstObfusUtil.LoadConstDouble(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + return true; + } + case 1: + { + // x = x * 1.0; + double a = 1.0; + float constProbability = 0f; + ConstObfusUtil.LoadConstDouble(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + case 2: + { + // x = (x + a) * b; a = 0.0, b = 1.0 + double a = 0.0; + double b = 1.0; + float constProbability = 0f; + ConstObfusUtil.LoadConstDouble(a, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstDouble(b, random, constProbability, ctx.constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + return true; + } + default: return false; + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs.meta new file mode 100644 index 00000000..d6a01e4e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/DefaultObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5200244f403139c40b578b2e845508f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs new file mode 100644 index 00000000..27697ad7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs @@ -0,0 +1,114 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.EvalStackObfus +{ + class ObfusMethodContext + { + public MethodDef method; + public EvalStackCalculator evalStackCalculator; + public LocalVariableAllocator localVariableAllocator; + public IRandom localRandom; + public EncryptionScopeInfo encryptionScope; + public DefaultMetadataImporter importer; + public ConstFieldAllocator constFieldAllocator; + public float obfuscationPercentage; + } + + internal class EvalStackObfusPass : ObfuscationMethodPassBase + { + private readonly EvalStackObfuscationSettingsFacade _settings; + + private IObfuscationPolicy _obfuscationPolicy; + private IObfuscator _obfuscator; + + public EvalStackObfusPass(EvalStackObfuscationSettingsFacade settings) + { + _settings = settings; + _obfuscator = new DefaultObfuscator(); + } + + public override ObfuscationPassType Type => ObfuscationPassType.EvalStackObfus; + + public override void Start() + { + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + _obfuscationPolicy = new ConfigurableObfuscationPolicy( + ctx.coreSettings.assembliesToObfuscate, + _settings.ruleFiles); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _obfuscationPolicy.NeedObfuscate(method); + } + + protected bool TryObfuscateInstruction(Instruction inst, EvalDataType dataType, List outputInstructions, ObfusMethodContext ctx) + { + switch (dataType) + { + case EvalDataType.Int32: return _obfuscator.ObfuscateInt(inst, outputInstructions, ctx); + case EvalDataType.Int64: return _obfuscator.ObfuscateLong(inst, outputInstructions, ctx); + case EvalDataType.Float: return _obfuscator.ObfuscateFloat(inst, outputInstructions, ctx); + case EvalDataType.Double: return _obfuscator.ObfuscateDouble(inst, outputInstructions, ctx); + default: return false; + } + } + + protected override void ObfuscateData(MethodDef method) + { + //Debug.Log($"Obfuscating method: {method.FullName} with EvalStackObfusPass"); + IList instructions = method.Body.Instructions; + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + var calc = new EvalStackCalculator(method); + + GroupByModuleEntityManager moduleEntityManager = ctx.moduleEntityManager; + var encryptionScope = moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + var ruleData = _obfuscationPolicy.GetObfuscationRuleData(method); + var localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)); + var obfusMethodCtx = new ObfusMethodContext + { + method = method, + evalStackCalculator = calc, + localVariableAllocator = new LocalVariableAllocator(method), + encryptionScope = encryptionScope, + constFieldAllocator = moduleEntityManager.GetEntity(method.Module), + localRandom = localRandom, + importer = moduleEntityManager.GetEntity(method.Module), + obfuscationPercentage = ruleData.obfuscationPercentage, + }; + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + totalFinalInstructions.Add(inst); + if (calc.TryGetPushResult(inst, out EvalDataType dataType) && localRandom.NextInPercentage(ruleData.obfuscationPercentage)) + { + outputInstructions.Clear(); + if (TryObfuscateInstruction(inst, dataType, outputInstructions, obfusMethodCtx)) + { + totalFinalInstructions.AddRange(outputInstructions); + } + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs.meta new file mode 100644 index 00000000..63a3a0c5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/EvalStackObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fa7d3313f260794da2cc36dadaf4fb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs new file mode 100644 index 00000000..79f8ff09 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs @@ -0,0 +1,24 @@ +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.EvalStackObfus +{ + interface IObfuscator + { + bool ObfuscateInt(Instruction inst, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateLong(Instruction inst, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateFloat(Instruction inst, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateDouble(Instruction inst, List outputInsts, ObfusMethodContext ctx); + } + + abstract class ObfuscatorBase : IObfuscator + { + public abstract bool ObfuscateInt(Instruction inst, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateLong(Instruction inst, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateFloat(Instruction inst, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateDouble(Instruction inst, List outputInsts, ObfusMethodContext ctx); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs.meta new file mode 100644 index 00000000..a782f110 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/EvalStackObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17a9f3181d9711f4ca1d0cfb9e813bb0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta new file mode 100644 index 00000000..24080ec4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9fdb2c243b1ea0f489e67233fda287c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 00000000..73929f1e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,143 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + struct ObfuscationRuleData + { + public readonly ObfuscationLevel obfuscationLevel; + public readonly float obfuscationPercentage; + public ObfuscationRuleData(ObfuscationLevel level, float percentage) + { + obfuscationLevel = level; + obfuscationPercentage = percentage; + } + } + + interface IObfuscationPolicy + { + bool NeedObfuscate(MethodDef method); + + ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscate(MethodDef method); + + public abstract ObfuscationRuleData GetObfuscationRuleData(MethodDef method); + } + + class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + class ObfuscationRule : IRule + { + public ObfuscationLevel? obfuscationLevel; + public float? obfuscationPercentage; + + public void InheritParent(ObfuscationRule parentRule) + { + if (obfuscationLevel == null) + obfuscationLevel = parentRule.obfuscationLevel; + if (obfuscationPercentage == null) + obfuscationPercentage = parentRule.obfuscationPercentage; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + obfuscationLevel = ObfuscationLevel.Basic, + obfuscationPercentage = 0.3f, + }; + + private ObfuscationRule _global; + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, ParseGlobal); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + + if (_global == null) + { + _global = s_default; + } + else + { + _global.InheritParent(s_default); + } + _xmlParser.InheritParentRules(_global); + } + + private void ParseGlobal(string configFile, XmlElement ele) + { + switch (ele.Name) + { + case "global": _global = ParseObfuscationRule(configFile, ele); break; + default: throw new Exception($"Invalid xml file {configFile}, unknown node {ele.Name}"); + } + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("obfuscationLevel")) + { + rule.obfuscationLevel = ConfigUtil.ParseObfuscationLevel(ele.GetAttribute("obfuscationLevel")); + } + if (ele.HasAttribute("obfuscationPercentage")) + { + rule.obfuscationPercentage = float.Parse(ele.GetAttribute("obfuscationPercentage")); + } + return rule; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, _global); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscate(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.obfuscationLevel.Value > ObfuscationLevel.None; + } + + public override ObfuscationRuleData GetObfuscationRuleData(MethodDef method) + { + var rule = GetMethodObfuscationRule(method); + return new ObfuscationRuleData(rule.obfuscationLevel.Value, rule.obfuscationPercentage.Value); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 00000000..2e67a4f5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f820a225c981b8499016958e6c69747 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs new file mode 100644 index 00000000..1a540e64 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs @@ -0,0 +1,173 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.ObfusPasses.ExprObfus.Obfuscators; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + class ObfusMethodContext + { + public MethodDef method; + public EvalStackCalculator evalStackCalculator; + public LocalVariableAllocator localVariableAllocator; + public IRandom localRandom; + public EncryptionScopeInfo encryptionScope; + public DefaultMetadataImporter importer; + public ConstFieldAllocator constFieldAllocator; + public float obfuscationPercentage; + } + + class ExprObfusPass : ObfuscationMethodPassBase + { + private readonly ExprObfuscationSettingsFacade _settings; + private readonly IObfuscator _basicObfuscator; + private readonly IObfuscator _advancedObfuscator; + private readonly IObfuscator _mostAdvancedObfuscator; + + private IObfuscationPolicy _obfuscationPolicy; + + public ExprObfusPass(ExprObfuscationSettingsFacade settings) + { + _settings = settings; + _basicObfuscator = new BasicObfuscator(); + _advancedObfuscator = new AdvancedObfuscator(); + _mostAdvancedObfuscator = new MostAdvancedObfuscator(); + } + + public override ObfuscationPassType Type => ObfuscationPassType.ExprObfus; + + public override void Start() + { + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + _obfuscationPolicy = new ConfigurableObfuscationPolicy( + ctx.coreSettings.assembliesToObfuscate, + _settings.ruleFiles); + } + + private IObfuscator GetObfuscator(ObfuscationLevel level) + { + switch (level) + { + case ObfuscationLevel.None: return null; + case ObfuscationLevel.Basic: return _basicObfuscator; + case ObfuscationLevel.Advanced: return _advancedObfuscator; + case ObfuscationLevel.MostAdvanced: return _mostAdvancedObfuscator; + default: throw new System.ArgumentOutOfRangeException(nameof(level), level, "Unknown obfuscation level"); + } + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return _obfuscationPolicy.NeedObfuscate(method); + } + + protected bool TryObfuscateInstruction(IObfuscator obfuscator, InstructionParameterInfo pi, Instruction inst, List outputInstructions, ObfusMethodContext ctx) + { + //Debug.Log($"Obfuscating instruction: {inst} in method: {ctx.method.FullName}"); + IRandom localRandom = ctx.localRandom; + float obfuscationPercentage = ctx.obfuscationPercentage; + switch (inst.OpCode.Code) + { + case Code.Neg: + { + return localRandom.NextInPercentage(obfuscationPercentage) && obfuscator.ObfuscateBasicUnaryOp(inst, pi.op1, pi.retType, outputInstructions, ctx); + } + case Code.Add: + case Code.Sub: + case Code.Mul: + case Code.Div: + case Code.Div_Un: + case Code.Rem: + case Code.Rem_Un: + { + return localRandom.NextInPercentage(obfuscationPercentage) && obfuscator.ObfuscateBasicBinOp(inst, pi.op1, pi.op2, pi.retType, outputInstructions, ctx); + } + case Code.And: + case Code.Or: + case Code.Xor: + { + return localRandom.NextInPercentage(obfuscationPercentage) && obfuscator.ObfuscateBinBitwiseOp(inst, pi.op1, pi.op2, pi.retType, outputInstructions, ctx); + } + case Code.Not: + { + return localRandom.NextInPercentage(obfuscationPercentage) && obfuscator.ObfuscateUnaryBitwiseOp(inst, pi.op1, pi.retType, outputInstructions, ctx); + } + case Code.Shl: + case Code.Shr: + case Code.Shr_Un: + { + return localRandom.NextInPercentage(obfuscationPercentage) && obfuscator.ObfuscateBitShiftOp(inst, pi.op1, pi.op2, pi.retType, outputInstructions, ctx); + } + } + return false; + } + + protected override void ObfuscateData(MethodDef method) + { + //Debug.Log($"Obfuscating method: {method.FullName} with ExprObfusPass"); + IList instructions = method.Body.Instructions; + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + var calc = new EvalStackCalculator(method); + + GroupByModuleEntityManager moduleEntityManager = ctx.moduleEntityManager; + var encryptionScope = moduleEntityManager.EncryptionScopeProvider.GetScope(method.Module); + var ruleData = _obfuscationPolicy.GetObfuscationRuleData(method); + var obfuscator = GetObfuscator(ruleData.obfuscationLevel); + var obfusMethodCtx = new ObfusMethodContext + { + method = method, + evalStackCalculator = calc, + localVariableAllocator = new LocalVariableAllocator(method), + encryptionScope = encryptionScope, + constFieldAllocator = moduleEntityManager.GetEntity(method.Module), + localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)), + importer = moduleEntityManager.GetEntity(method.Module), + obfuscationPercentage = ruleData.obfuscationPercentage, + }; + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + bool add = false; + if (calc.TryGetParameterInfo(inst, out InstructionParameterInfo pi)) + { + outputInstructions.Clear(); + if (TryObfuscateInstruction(obfuscator, pi, inst, outputInstructions, obfusMethodCtx)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + add = true; + } + } + if (!add) + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta new file mode 100644 index 00000000..f108ce32 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 477e081ffc0072e4fa1a06100269e4a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs new file mode 100644 index 00000000..605f6438 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs @@ -0,0 +1,28 @@ +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + interface IObfuscator + { + bool ObfuscateBasicUnaryOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateBasicBinOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateUnaryBitwiseOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateBinBitwiseOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + + bool ObfuscateBitShiftOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + } + + abstract class ObfuscatorBase : IObfuscator + { + public abstract bool ObfuscateBasicUnaryOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateBasicBinOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateUnaryBitwiseOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateBinBitwiseOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + public abstract bool ObfuscateBitShiftOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta new file mode 100644 index 00000000..a72c8645 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a88981a87bcd9e84b883e39c81cfbf44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators.meta new file mode 100644 index 00000000..dde5f438 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4c5dc8736831c9f4b934c69f7894a412 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs new file mode 100644 index 00000000..f055962a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs @@ -0,0 +1,110 @@ +using dnlib.DotNet.Emit; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + class AdvancedObfuscator : BasicObfuscator + { + protected bool GenerateIdentityTransformForArgument(Instruction inst, EvalDataType op, List outputInsts, ObfusMethodContext ctx) + { + IRandom random = ctx.localRandom; + ConstFieldAllocator constFieldAllocator = ctx.constFieldAllocator; + switch (op) + { + case EvalDataType.Int32: + { + // = x + y = x + (y * a + b) * ra + (-b * ra) + int a = random.NextInt() | 0x1; + int ra = MathUtil.ModInverse32(a); + int b = random.NextInt(); + int b_ra = -b * ra; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstInt(a, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(b, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstInt(ra, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstInt(b_ra, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + outputInsts.Add(inst.Clone()); + return true; + } + case EvalDataType.Int64: + { + // = x + y = x + (y * a + b) * ra + (-b * ra) + long a = random.NextLong() | 0x1L; + long ra = MathUtil.ModInverse64(a); + long b = random.NextLong(); + long b_ra = -b * ra; + float constProbability = 0.5f; + ConstObfusUtil.LoadConstLong(a, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(b, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstLong(ra, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + ConstObfusUtil.LoadConstLong(b_ra, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + outputInsts.Add(inst.Clone()); + return true; + } + case EvalDataType.Float: + { + // = x + y = x + (y + a) * b; a = 0.0f, b = 1.0f + float a = 0.0f; + float b = 1.0f; + float constProbability = 0f; + ConstObfusUtil.LoadConstFloat(a, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstFloat(b, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + outputInsts.Add(inst.Clone()); + return true; + } + case EvalDataType.Double: + { + // = x + y = x + (y + a) * b; a = 0.0, b = 1.0 + double a = 0.0; + double b = 1.0; + float constProbability = 0f; + ConstObfusUtil.LoadConstDouble(a, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Add)); + ConstObfusUtil.LoadConstDouble(b, random, constProbability, constFieldAllocator, outputInsts); + outputInsts.Add(Instruction.Create(OpCodes.Mul)); + outputInsts.Add(inst.Clone()); + return true; + } + default: return false; + } + } + + public override bool ObfuscateBasicUnaryOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + return GenerateIdentityTransformForArgument(inst, op, outputInsts, ctx) || base.ObfuscateBasicUnaryOp(inst, op, ret, outputInsts, ctx); + } + + public override bool ObfuscateBasicBinOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + return GenerateIdentityTransformForArgument(inst, op2, outputInsts, ctx) || base.ObfuscateBasicBinOp(inst, op1, op2, ret, outputInsts, ctx); + } + + public override bool ObfuscateUnaryBitwiseOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + return GenerateIdentityTransformForArgument(inst, op, outputInsts, ctx) || base.ObfuscateUnaryBitwiseOp(inst, op, ret, outputInsts, ctx); + } + + public override bool ObfuscateBinBitwiseOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + return GenerateIdentityTransformForArgument(inst, op2, outputInsts, ctx) || base.ObfuscateBinBitwiseOp(inst, op1, op2, ret, outputInsts, ctx); + } + + public override bool ObfuscateBitShiftOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + return GenerateIdentityTransformForArgument(inst, op2, outputInsts, ctx) || base.ObfuscateBitShiftOp(inst, op1, op2, ret, outputInsts, ctx); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta new file mode 100644 index 00000000..06cf2c52 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef717515402ca2f41a52db7ea1300f32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs new file mode 100644 index 00000000..56e110e4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs @@ -0,0 +1,282 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + + class BasicObfuscator : ObfuscatorBase + { + private IMethod GetUnaryOpMethod(DefaultMetadataImporter importer, Code code, EvalDataType op1) + { + switch (code) + { + case Code.Neg: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NegInt; + case EvalDataType.Int64: return importer.NegLong; + case EvalDataType.Float: return importer.NegFloat; + case EvalDataType.Double: return importer.NegDouble; + default: return null; + } + } + case Code.Not: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NotInt; + case EvalDataType.Int64: return importer.NotLong; + default: return null; + } + } + default: return null; + } + } + + private IMethod GetBinaryOpMethod(DefaultMetadataImporter importer, Code code, EvalDataType op1, EvalDataType op2) + { + switch (code) + { + case Code.Add: + { + switch (op1) + { + case EvalDataType.Int32: return op2 == op1 ? importer.AddInt : null; + case EvalDataType.Int64: return op2 == op1 ? importer.AddLong : null; + case EvalDataType.Float: return op2 == op1 ? importer.AddFloat : null; + case EvalDataType.Double: return op2 == op1 ? importer.AddDouble : null; + case EvalDataType.I: + { + switch (op2) + { + case EvalDataType.I: return importer.AddIntPtr; + case EvalDataType.Int32: return importer.AddIntPtrInt; + default: return null; + } + } + default: return null; + } + } + case Code.Sub: + { + switch (op1) + { + case EvalDataType.Int32: return op2 == op1 ? importer.SubtractInt : null; + case EvalDataType.Int64: return op2 == op1 ? importer.SubtractLong : null; + case EvalDataType.Float: return op2 == op1 ? importer.SubtractFloat : null; + case EvalDataType.Double: return op2 == op1 ? importer.SubtractDouble : null; + case EvalDataType.I: + { + switch (op2) + { + case EvalDataType.I: return importer.SubtractIntPtr; + case EvalDataType.Int32: return importer.SubtractIntPtrInt; + default: return null; + } + } + default: return null; + } + } + case Code.Mul: + { + switch (op1) + { + case EvalDataType.Int32: return op2 == op1 ? importer.MultiplyInt : null; + case EvalDataType.Int64: return op2 == op1 ? importer.MultiplyLong : null; + case EvalDataType.Float: return op2 == op1 ? importer.MultiplyFloat : null; + case EvalDataType.Double: return op2 == op1 ? importer.MultiplyDouble : null; + case EvalDataType.I: + { + switch (op2) + { + case EvalDataType.I: return importer.MultiplyIntPtr; + case EvalDataType.Int32: return importer.MultiplyIntPtrInt; + default: return null; + } + } + default: return null; + } + } + case Code.Div: + { + switch (op1) + { + case EvalDataType.Int32: return importer.DivideInt; + case EvalDataType.Int64: return importer.DivideLong; + case EvalDataType.Float: return importer.DivideFloat; + case EvalDataType.Double: return importer.DivideDouble; + default: return null; + } + } + case Code.Div_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.DivideUnInt; + case EvalDataType.Int64: return importer.DivideUnLong; + default: return null; + } + } + case Code.Rem: + { + switch (op1) + { + case EvalDataType.Int32: return importer.RemInt; + case EvalDataType.Int64: return importer.RemLong; + case EvalDataType.Float: return importer.RemFloat; + case EvalDataType.Double: return importer.RemDouble; + default: return null; + } + } + case Code.Rem_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.RemUnInt; + case EvalDataType.Int64: return importer.RemUnLong; + default: return null; + } + } + case Code.Neg: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NegInt; + case EvalDataType.Int64: return importer.NegLong; + case EvalDataType.Float: return importer.NegFloat; + case EvalDataType.Double: return importer.NegDouble; + default: return null; + } + } + case Code.And: + { + switch (op1) + { + case EvalDataType.Int32: return importer.AndInt; + case EvalDataType.Int64: return importer.AndLong; + default: return null; + } + } + case Code.Or: + { + switch (op1) + { + case EvalDataType.Int32: return importer.OrInt; + case EvalDataType.Int64: return importer.OrLong; + default: return null; + } + } + case Code.Xor: + { + switch (op1) + { + case EvalDataType.Int32: return importer.XorInt; + case EvalDataType.Int64: return importer.XorLong; + default: return null; + } + } + case Code.Not: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NotInt; + case EvalDataType.Int64: return importer.NotLong; + default: return null; + } + } + case Code.Shl: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShlInt; + case EvalDataType.Int64: return importer.ShlLong; + default: return null; + } + } + case Code.Shr: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShrInt; + case EvalDataType.Int64: return importer.ShrLong; + default: return null; + } + } + case Code.Shr_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShrUnInt; + case EvalDataType.Int64: return importer.ShrUnLong; + default: return null; + } + } + default: return null; + } + } + + public override bool ObfuscateBasicUnaryOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + IMethod opMethod = GetUnaryOpMethod(ctx.importer, inst.OpCode.Code, op); + if (opMethod == null) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate unary operation {inst.OpCode.Code} with different operand types: op={op}. This is a limitation of the BasicObfuscator."); + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBasicBinOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + IMethod opMethod = GetBinaryOpMethod(ctx.importer, inst.OpCode.Code, op1, op2); + if (opMethod == null) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with different operand types: op1={op1}, op2={op2}, ret={ret}. This is a limitation of the BasicObfuscator."); + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateUnaryBitwiseOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + IMethod opMethod = GetUnaryOpMethod(ctx.importer, inst.OpCode.Code, op); + if (opMethod == null) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate unary operation {inst.OpCode.Code} with different operand types: op={op}. This is a limitation of the BasicObfuscator."); + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBinBitwiseOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + IMethod opMethod = GetBinaryOpMethod(ctx.importer, inst.OpCode.Code, op1, op2); + if (opMethod == null) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with different operand types: op1={op1}, op2={op2}, ret={ret}. This is a limitation of the BasicObfuscator."); + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBitShiftOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + IMethod opMethod = GetBinaryOpMethod(ctx.importer, inst.OpCode.Code, op1, op2); + if (opMethod == null) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with operand type {op2}. This is a limitation of the BasicObfuscator."); + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta new file mode 100644 index 00000000..dd49d805 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 578caeae17526b54c9ff1979d897feb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs new file mode 100644 index 00000000..1ff1a70b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs @@ -0,0 +1,83 @@ +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + class MostAdvancedObfuscator : AdvancedObfuscator + { + private readonly BasicObfuscator _basicObfuscator = new BasicObfuscator(); + + public override bool ObfuscateBasicUnaryOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + if (!base.ObfuscateBasicUnaryOp(inst, op, ret, outputInsts, ctx)) + { + return false; + } + if (outputInsts.Last().OpCode.Code != inst.OpCode.Code) + { + return false; + } + outputInsts.RemoveAt(outputInsts.Count - 1); + return _basicObfuscator.ObfuscateBasicUnaryOp(inst, op, ret, outputInsts, ctx); + } + + public override bool ObfuscateBasicBinOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + if (!base.ObfuscateBasicBinOp(inst, op1, op2, ret, outputInsts, ctx)) + { + return false; + } + if (outputInsts.Last().OpCode.Code != inst.OpCode.Code) + { + return false; + } + outputInsts.RemoveAt(outputInsts.Count - 1); + return _basicObfuscator.ObfuscateBasicBinOp(inst, op1, op2, ret, outputInsts, ctx); + } + + public override bool ObfuscateUnaryBitwiseOp(Instruction inst, EvalDataType op, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + if (!base.ObfuscateUnaryBitwiseOp(inst, op, ret, outputInsts, ctx)) + { + return false; + } + + if (outputInsts.Last().OpCode.Code != inst.OpCode.Code) + { + return false; + } + outputInsts.RemoveAt(outputInsts.Count - 1); + return _basicObfuscator.ObfuscateUnaryBitwiseOp(inst, op, ret, outputInsts, ctx); + } + + public override bool ObfuscateBinBitwiseOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + if (!base.ObfuscateBinBitwiseOp(inst, op1, op2, ret, outputInsts, ctx)) + { + return false; + } + if (outputInsts.Last().OpCode.Code != inst.OpCode.Code) + { + return false; + } + outputInsts.RemoveAt(outputInsts.Count - 1); + return _basicObfuscator.ObfuscateBinBitwiseOp(inst, op1, op2, ret, outputInsts, ctx); + } + + public override bool ObfuscateBitShiftOp(Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, List outputInsts, ObfusMethodContext ctx) + { + if (!base.ObfuscateBitShiftOp(inst, op1, op2, ret, outputInsts, ctx)) + { + return false; + } + if (outputInsts.Last().OpCode.Code != inst.OpCode.Code) + { + return false; + } + outputInsts.RemoveAt(outputInsts.Count - 1); + return _basicObfuscator.ObfuscateBitShiftOp(inst, op1, op2, ret, outputInsts, ctx); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta new file mode 100644 index 00000000..fb1660a3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af5946ac6cb0a8b4fa75321439785133 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta new file mode 100644 index 00000000..24c7b1d7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0b789725c4848bd4fb4b3ce1f2e2a9c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs new file mode 100644 index 00000000..31d977e7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs @@ -0,0 +1,45 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Utils; +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class ConfigurableEncryptPolicy : EncryptPolicyBase + { + class ObfuscationRule + { + + } + + private readonly XmlFieldRuleParser _configParser; + private readonly ObfuzIgnoreScopeComputeCache _obfuzIgnoreScopeComputeCache; + + public ConfigurableEncryptPolicy(ObfuzIgnoreScopeComputeCache obfuzIgnoreScopeComputeCache, List toObfuscatedAssemblyNames, List configFiles) + { + _obfuzIgnoreScopeComputeCache = obfuzIgnoreScopeComputeCache; + _configParser = new XmlFieldRuleParser(toObfuscatedAssemblyNames, ParseRule, null); + _configParser.LoadConfigs(configFiles); + } + + private ObfuscationRule ParseRule(string configFile, XmlElement ele) + { + return new ObfuscationRule(); + } + + public override bool NeedEncrypt(FieldDef field) + { + if (MetaUtil.HasEncryptFieldAttribute(field)) + { + return true; + } + if (_obfuzIgnoreScopeComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(field, field.DeclaringType, ObfuzScope.Field)) + { + return false; + } + var rule = _configParser.GetFieldRule(field); + return rule != null; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta new file mode 100644 index 00000000..bcc4d60c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/ConfigurableEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b17fa09ce58526459f2b9e375c31cad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs new file mode 100644 index 00000000..bc8bbe9a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs @@ -0,0 +1,201 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class DefaultFieldEncryptor : FieldEncryptorBase + { + private readonly GroupByModuleEntityManager _moduleEntityManager; + private readonly FieldEncryptionSettingsFacade _settings; + + public DefaultFieldEncryptor(GroupByModuleEntityManager moduleEntityManager, FieldEncryptionSettingsFacade settings) + { + _moduleEntityManager = moduleEntityManager; + _settings = settings; + } + + class FieldEncryptInfo + { + public int encryptOps; + public int salt; + public ElementType fieldType; + public long xorValueForZero; + } + + private readonly Dictionary _fieldEncryptInfoCache = new Dictionary(); + + + private long CalcXorValueForZero(IEncryptor encryptor, ElementType type, int encryptOps, int salt) + { + switch (type) + { + case ElementType.I4: + case ElementType.U4: + case ElementType.R4: + return encryptor.Encrypt(0, encryptOps, salt); + case ElementType.I8: + case ElementType.U8: + case ElementType.R8: + return encryptor.Encrypt(0L, encryptOps, salt); + default: + throw new NotSupportedException($"Unsupported field type: {type} for encryption"); + } + } + + + private IRandom CreateRandomForField(RandomCreator randomCreator, FieldDef field) + { + return randomCreator(FieldEqualityComparer.CompareDeclaringTypes.GetHashCode(field)); + } + + private int GenerateEncryptionOperations(IRandom random, IEncryptor encryptor) + { + return EncryptionUtil.GenerateEncryptionOpCodes(random, encryptor, _settings.encryptionLevel); + } + + public int GenerateSalt(IRandom random) + { + return random.NextInt(); + } + + private FieldEncryptInfo GetFieldEncryptInfo(FieldDef field) + { + if (_fieldEncryptInfoCache.TryGetValue(field, out var info)) + { + return info; + } + EncryptionScopeInfo encryptionScope = _moduleEntityManager.EncryptionScopeProvider.GetScope(field.Module); + + IRandom random = CreateRandomForField(encryptionScope.localRandomCreator, field); + IEncryptor encryptor = encryptionScope.encryptor; + int encryptOps = GenerateEncryptionOperations(random, encryptor); + int salt = GenerateSalt(random); + ElementType fieldType = field.FieldSig.Type.RemovePinnedAndModifiers().ElementType; + long xorValueForZero = CalcXorValueForZero(encryptor, fieldType, encryptOps, salt); + + info = new FieldEncryptInfo + { + encryptOps = encryptOps, + salt = salt, + fieldType = fieldType, + xorValueForZero = xorValueForZero, + }; + _fieldEncryptInfoCache[field] = info; + return info; + } + + public override void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + DefaultMetadataImporter importer = _moduleEntityManager.GetEntity(method.Module); + EncryptionServiceMetadataImporter encryptionServiceMetadataImporter = importer.GetEncryptionServiceMetadataImporterOfModule(field.Module); + FieldEncryptInfo fei = GetFieldEncryptInfo(field); + if (fei.fieldType == ElementType.I4 || fei.fieldType == ElementType.U4 || fei.fieldType == ElementType.R4) + { + // value has been put on stack + + if (fei.fieldType == ElementType.R4) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastFloatAsInt)); + } + // encrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.EncryptInt)); + // xor + outputInstructions.Add(Instruction.CreateLdcI4((int)fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + + if (fei.fieldType == ElementType.R4) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastIntAsFloat)); + } + } + else if (fei.fieldType == ElementType.I8 || fei.fieldType == ElementType.U8 || fei.fieldType == ElementType.R8) + { + // value has been put on stack + if (fei.fieldType == ElementType.R8) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastDoubleAsLong)); + } + + // encrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.EncryptLong)); + // xor + outputInstructions.Add(Instruction.Create(OpCodes.Ldc_I8, fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + if (fei.fieldType == ElementType.R8) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastLongAsDouble)); + } + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for encryption"); + } + + outputInstructions.Add(currentInstruction.Clone()); + } + + public override void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction) + { + outputInstructions.Add(currentInstruction.Clone()); + DefaultMetadataImporter importer = _moduleEntityManager.GetEntity(method.Module); + EncryptionServiceMetadataImporter encryptionServiceMetadataImporter = importer.GetEncryptionServiceMetadataImporterOfModule(field.Module); + FieldEncryptInfo fei = GetFieldEncryptInfo(field); + if (fei.fieldType == ElementType.I4 || fei.fieldType == ElementType.U4 || fei.fieldType == ElementType.R4) + { + // value has been put on stack + // xor + if (fei.fieldType == ElementType.R4) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastFloatAsInt)); + } + outputInstructions.Add(Instruction.CreateLdcI4((int)fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + + // decrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.DecryptInt)); + + if (fei.fieldType == ElementType.R4) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastIntAsFloat)); + } + } + else if (fei.fieldType == ElementType.I8 || fei.fieldType == ElementType.U8 || fei.fieldType == ElementType.R8) + { + // value has been put on stack + // xor + if (fei.fieldType == ElementType.R8) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastDoubleAsLong)); + } + outputInstructions.Add(Instruction.Create(OpCodes.Ldc_I8, fei.xorValueForZero)); + outputInstructions.Add(Instruction.Create(OpCodes.Xor)); + + // decrypt + outputInstructions.Add(Instruction.CreateLdcI4(fei.encryptOps)); + outputInstructions.Add(Instruction.CreateLdcI4(fei.salt)); + outputInstructions.Add(Instruction.Create(OpCodes.Call, encryptionServiceMetadataImporter.DecryptLong)); + + if (fei.fieldType == ElementType.R8) + { + outputInstructions.Add(Instruction.Create(OpCodes.Call, importer.CastLongAsDouble)); + } + } + else + { + Assert.IsTrue(false, $"Unsupported field type: {fei.fieldType} for decryption"); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta new file mode 100644 index 00000000..905f446b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/DefaultFieldEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6707a66ae63e2c498d55088c6e8ef4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs new file mode 100644 index 00000000..3f82faf5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs @@ -0,0 +1,102 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Settings; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + + public class FieldEncryptPass : InstructionObfuscationPassBase + { + private FieldEncryptionSettingsFacade _settings; + private IEncryptPolicy _encryptionPolicy; + private IFieldEncryptor _memoryEncryptor; + + public override ObfuscationPassType Type => ObfuscationPassType.FieldEncrypt; + + public FieldEncryptPass(FieldEncryptionSettingsFacade settings) + { + _settings = settings; + } + + protected override bool ForceProcessAllAssembliesAndIgnoreAllPolicy => true; + + public override void Start() + { + var ctx = ObfuscationPassContext.Current; + _memoryEncryptor = new DefaultFieldEncryptor(ctx.moduleEntityManager, _settings); + _encryptionPolicy = new ConfigurableEncryptPolicy(ctx.obfuzIgnoreScopeComputeCache, ctx.coreSettings.assembliesToObfuscate, _settings.ruleFiles); + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return true; + } + + private bool IsSupportedFieldType(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.I4: + case ElementType.I8: + case ElementType.U4: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + return true; + default: return false; + } + } + + protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) + { + Code code = inst.OpCode.Code; + if (!(inst.Operand is IField field) || !field.IsField) + { + return false; + } + FieldDef fieldDef = field.ResolveFieldDefThrow(); + if (!IsSupportedFieldType(fieldDef.FieldSig.Type) || !_encryptionPolicy.NeedEncrypt(fieldDef)) + { + return false; + } + switch (code) + { + case Code.Ldfld: + { + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Stfld: + { + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Ldsfld: + { + _memoryEncryptor.Decrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Stsfld: + { + _memoryEncryptor.Encrypt(callingMethod, fieldDef, outputInstructions, inst); + break; + } + case Code.Ldflda: + case Code.Ldsflda: + { + throw new System.Exception($"You shouldn't get reference to memory encryption field: {field}"); + } + default: return false; + } + //Debug.Log($"memory encrypt field: {field}"); + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta new file mode 100644 index 00000000..1e60f28c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/FieldEncryptPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3da24d0f1f1fc7449cbd0e7ddd03aa2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs new file mode 100644 index 00000000..ee7dd534 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs @@ -0,0 +1,14 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public interface IEncryptPolicy + { + bool NeedEncrypt(FieldDef field); + } + + public abstract class EncryptPolicyBase : IEncryptPolicy + { + public abstract bool NeedEncrypt(FieldDef field); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta new file mode 100644 index 00000000..d5c10473 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IEncryptPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a48d0500d0737404cad9c9ef23a9467c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs new file mode 100644 index 00000000..0b6d5821 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs @@ -0,0 +1,26 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.FieldEncrypt +{ + public class MemoryEncryptionContext + { + public ModuleDef module; + + public Instruction currentInstruction; + } + + public interface IFieldEncryptor + { + void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + + void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } + + public abstract class FieldEncryptorBase : IFieldEncryptor + { + public abstract void Encrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + public abstract void Decrypt(MethodDef method, FieldDef field, List outputInstructions, Instruction currentInstruction); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta new file mode 100644 index 00000000..dca50715 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/FieldEncrypt/IFieldEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a3ec14fca5169d479529d21b2eeada1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct.meta new file mode 100644 index 00000000..97e300cc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bb4f71e54c6a07341883ba0c642505c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs new file mode 100644 index 00000000..f7b32753 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs @@ -0,0 +1,138 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Editor; +using Obfuz.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.Instinct +{ + + public class InstinctPass : InstructionObfuscationPassBase + { + public override ObfuscationPassType Type => ObfuscationPassType.None; + + protected override bool ForceProcessAllAssembliesAndIgnoreAllPolicy => true; + + public InstinctPass() + { + } + + public override void Start() + { + } + + public override void Stop() + { + + } + + protected override bool NeedObfuscateMethod(MethodDef method) + { + return true; + } + + private string GetTypeName(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Class: + case ElementType.ValueType: + { + return type.ReflectionName; + } + case ElementType.GenericInst: + { + type = ((GenericInstSig)type).GenericType; + return type.ReflectionName; + } + default: return type.ReflectionName; + } + } + + private string GetTypeFullName(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + + switch (type.ElementType) + { + case ElementType.Class: + case ElementType.ValueType: + { + return type.ReflectionFullName; + } + case ElementType.GenericInst: + { + GenericInstSig genericInstSig = (GenericInstSig)type; + var typeName = new StringBuilder(genericInstSig.GenericType.ReflectionFullName); + typeName.Append("<").Append(string.Join(",", genericInstSig.GenericArguments.Select(GetTypeFullName))).Append(">"); + return typeName.ToString(); + } + default: return type.ReflectionFullName; + } + } + + protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) + { + Code code = inst.OpCode.Code; + if (!(inst.Operand is IMethod method) || !method.IsMethod) + { + return false; + } + MethodDef methodDef = method.ResolveMethodDef(); + if (methodDef == null || methodDef.DeclaringType.Name != "ObfuscationInstincts" || methodDef.DeclaringType.DefinitionAssembly.Name != ConstValues.ObfuzRuntimeAssemblyName) + { + return false; + } + + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + var importer = ctx.moduleEntityManager.GetEntity(callingMethod.Module); + + string methodName = methodDef.Name; + switch (methodName) + { + case "FullNameOf": + case "NameOf": + case "RegisterReflectionType": + { + MethodSpec methodSpec = (MethodSpec)method; + GenericInstMethodSig gims = methodSpec.GenericInstMethodSig; + Assert.AreEqual(1, gims.GenericArguments.Count, "FullNameOf should have exactly one generic argument"); + TypeSig type = gims.GenericArguments[0]; + switch (methodName) + { + case "FullNameOf": + { + string typeFullName = GetTypeFullName(type); + outputInstructions.Add(Instruction.Create(OpCodes.Ldstr, typeFullName)); + break; + } + case "NameOf": + { + string typeName = GetTypeName(type); + outputInstructions.Add(Instruction.Create(OpCodes.Ldstr, typeName)); + break; + } + case "RegisterReflectionType": + { + string typeFullName = GetTypeFullName(type); + outputInstructions.Add(Instruction.Create(OpCodes.Ldstr, typeFullName)); + var finalMethod = new MethodSpecUser((IMethodDefOrRef)importer.ObfuscationTypeMapperRegisterType, gims); + outputInstructions.Add(Instruction.Create(OpCodes.Call, finalMethod)); + break; + } + default: throw new NotSupportedException($"Unsupported instinct method: {methodDef.FullName}"); + } + break; + } + default: throw new NotSupportedException($"Unsupported instinct method: {methodDef.FullName}"); + } + //Debug.Log($"memory encrypt field: {field}"); + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs.meta new file mode 100644 index 00000000..ca21ae47 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/Instinct/InstinctPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08027d16e09664c40b561715ef9326fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs new file mode 100644 index 00000000..424c0d20 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs @@ -0,0 +1,46 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses +{ + public abstract class InstructionObfuscationPassBase : ObfuscationMethodPassBase + { + protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, + List outputInstructions, List totalFinalInstructions); + + protected override void ObfuscateData(MethodDef method) + { + IList instructions = method.Body.Instructions; + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, inst, instructions, i, outputInstructions, totalFinalInstructions)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + } + else + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta new file mode 100644 index 00000000..ba2497af --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/InstructionObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0cad4b764050f44f8c9b225056a4f49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs new file mode 100644 index 00000000..7a2e0942 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs @@ -0,0 +1,47 @@ +using dnlib.DotNet; +using System.Linq; + +namespace Obfuz.ObfusPasses +{ + public abstract class ObfuscationMethodPassBase : ObfuscationPassBase + { + protected virtual bool ForceProcessAllAssembliesAndIgnoreAllPolicy => false; + + protected abstract bool NeedObfuscateMethod(MethodDef method); + + protected abstract void ObfuscateData(MethodDef method); + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + var modules = ForceProcessAllAssembliesAndIgnoreAllPolicy ? ctx.allObfuscationRelativeModules : ctx.modulesToObfuscate; + ObfuscationMethodWhitelist whiteList = ctx.whiteList; + ConfigurablePassPolicy passPolicy = ctx.passPolicy; + foreach (ModuleDef mod in modules) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(mod)) + { + continue; + } + // ToArray to avoid modify list exception + foreach (TypeDef type in mod.GetTypes().ToArray()) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(type)) + { + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)))) + { + continue; + } + // TODO if isGeneratedBy Obfuscator, continue + ObfuscateData(method); + } + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta new file mode 100644 index 00000000..0be4d9f9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84b0592af70b0cc41b546cf8ac39f889 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs new file mode 100644 index 00000000..48e23ec8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs @@ -0,0 +1,18 @@ +namespace Obfuz.ObfusPasses +{ + public abstract class ObfuscationPassBase : IObfuscationPass + { + public abstract ObfuscationPassType Type { get; } + + public bool Support(ObfuscationPassType passType) + { + return passType.HasFlag(Type); + } + + public abstract void Start(); + + public abstract void Stop(); + + public abstract void Process(); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta new file mode 100644 index 00000000..335ee271 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f3e7e1d2a3ad3a4fb1a81e97730b5a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs new file mode 100644 index 00000000..b5deb968 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs @@ -0,0 +1,26 @@ +using System; + +namespace Obfuz.ObfusPasses +{ + [Flags] + public enum ObfuscationPassType + { + None = 0, + + ConstEncrypt = 0x1, + FieldEncrypt = 0x2, + + SymbolObfus = 0x100, + CallObfus = 0x200, + ExprObfus = 0x400, + ControlFlowObfus = 0x800, + EvalStackObfus = 0x1000, + + AllObfus = SymbolObfus | CallObfus | ExprObfus | ControlFlowObfus | EvalStackObfus, + AllEncrypt = ConstEncrypt | FieldEncrypt, + + MethodBodyObfusOrEncrypt = ConstEncrypt | CallObfus | ExprObfus | ControlFlowObfus | EvalStackObfus, + + All = ~0, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta new file mode 100644 index 00000000..b03ea206 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/ObfuscationPassType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5addd02f6f3dc0a4d888a0f74bd5ce4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta new file mode 100644 index 00000000..331b3d15 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b746569f7c0d9754fa6f2925538eddbd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs new file mode 100644 index 00000000..6f95ffdb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs @@ -0,0 +1,37 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public interface INameMaker + { + void AddPreservedName(TypeDef typeDef, string name); + + void AddPreservedNamespace(TypeDef typeDef, string name); + + void AddPreservedName(MethodDef methodDef, string name); + + void AddPreservedName(FieldDef fieldDef, string name); + + void AddPreservedName(PropertyDef propertyDef, string name); + + void AddPreservedName(EventDef eventDef, string name); + + bool IsNamePreserved(VirtualMethodGroup virtualMethodGroup, string name); + + string GetNewName(TypeDef typeDef, string originalName); + + string GetNewNamespace(TypeDef typeDef, string originalNamespace, bool reuse); + + string GetNewName(MethodDef methodDef, string originalName); + + string GetNewName(VirtualMethodGroup virtualMethodGroup, string originalName); + + string GetNewName(ParamDef param, string originalName); + + string GetNewName(FieldDef fieldDef, string originalName); + + string GetNewName(PropertyDef propertyDef, string originalName); + + string GetNewName(EventDef eventDef, string originalName); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta new file mode 100644 index 00000000..80621974 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/INameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c24d29f654d00b44bb6aa3b4bf222dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs new file mode 100644 index 00000000..2a39e451 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs @@ -0,0 +1,17 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public interface IObfuscationPolicy + { + bool NeedRename(TypeDef typeDef); + + bool NeedRename(MethodDef methodDef); + + bool NeedRename(FieldDef fieldDef); + + bool NeedRename(PropertyDef propertyDef); + + bool NeedRename(EventDef eventDef); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta new file mode 100644 index 00000000..ffe0fee1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd640b26c1d868544a7a91a0f986fdde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta new file mode 100644 index 00000000..9cd6e532 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c970ffd992fbc154aaa37a2c48c24d5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs new file mode 100644 index 00000000..038281e4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs @@ -0,0 +1,31 @@ +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public class DebugNameMaker : NameMakerBase + { + private class DebugNameScope : INameScope + { + + public bool AddPreservedName(string name) + { + return true; + } + + public string GetNewName(string originalName, bool reuse) + { + return $"${originalName}"; + } + + public bool IsNamePreserved(string name) + { + return false; + } + } + + protected override INameScope CreateNameScope() + { + return new DebugNameScope(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta new file mode 100644 index 00000000..11b505b8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/DebugNameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abc1adfad5c7754499ceed4d4646eb58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs new file mode 100644 index 00000000..c6ca426f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs @@ -0,0 +1,11 @@ +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public interface INameScope + { + bool AddPreservedName(string name); + + bool IsNamePreserved(string name); + + string GetNewName(string originalName, bool reuse); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta new file mode 100644 index 00000000..4c632535 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/INameScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c3884d338faf564eab48d58f02adc39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs new file mode 100644 index 00000000..1cd72c95 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs @@ -0,0 +1,116 @@ +using dnlib.DotNet; +using System.Collections.Generic; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public abstract class NameMakerBase : INameMaker + { + + private readonly Dictionary _nameScopes = new Dictionary(); + + private readonly object _namespaceScope = new object(); + private readonly object _typeNameScope = new object(); + private readonly object _methodNameScope = new object(); + private readonly object _fieldNameScope = new object(); + + protected abstract INameScope CreateNameScope(); + + protected INameScope GetNameScope(object key) + { + if (!_nameScopes.TryGetValue(key, out var nameScope)) + { + nameScope = CreateNameScope(); + _nameScopes[key] = nameScope; + } + return nameScope; + } + + public void AddPreservedName(TypeDef typeDef, string name) + { + GetNameScope(_typeNameScope).AddPreservedName(name); + } + + public void AddPreservedName(MethodDef methodDef, string name) + { + GetNameScope(_methodNameScope).AddPreservedName(name); + } + + public void AddPreservedName(FieldDef fieldDef, string name) + { + GetNameScope(_fieldNameScope).AddPreservedName(name); + } + + public void AddPreservedName(PropertyDef propertyDef, string name) + { + GetNameScope(propertyDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedName(EventDef eventDef, string name) + { + GetNameScope(eventDef.DeclaringType).AddPreservedName(name); + } + + public void AddPreservedNamespace(TypeDef typeDef, string name) + { + GetNameScope(_namespaceScope).AddPreservedName(name); + } + + public bool IsNamePreserved(VirtualMethodGroup virtualMethodGroup, string name) + { + var scope = GetNameScope(_methodNameScope); + return scope.IsNamePreserved(name); + } + + private string GetDefaultNewName(object scope, string originName) + { + return GetNameScope(scope).GetNewName(originName, false); + } + + public string GetNewNamespace(TypeDef typeDef, string originalNamespace, bool reuse) + { + if (string.IsNullOrEmpty(originalNamespace)) + { + return string.Empty; + } + return GetNameScope(_namespaceScope).GetNewName(originalNamespace, reuse); + } + + public string GetNewName(TypeDef typeDef, string originalName) + { + return GetDefaultNewName(_typeNameScope, originalName); + } + + public string GetNewName(MethodDef methodDef, string originalName) + { + Assert.IsFalse(methodDef.IsVirtual); + return GetDefaultNewName(_methodNameScope, originalName); + } + + public string GetNewName(VirtualMethodGroup virtualMethodGroup, string originalName) + { + var scope = GetNameScope(_methodNameScope); + return scope.GetNewName(originalName, false); + } + + public virtual string GetNewName(ParamDef param, string originalName) + { + return "1"; + } + + public string GetNewName(FieldDef fieldDef, string originalName) + { + return GetDefaultNewName(_fieldNameScope, originalName); + } + + public string GetNewName(PropertyDef propertyDef, string originalName) + { + return GetDefaultNewName(propertyDef.DeclaringType, originalName); + } + + public string GetNewName(EventDef eventDef, string originalName) + { + return GetDefaultNewName(eventDef.DeclaringType, originalName); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta new file mode 100644 index 00000000..d3ca8a02 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 205da3a8ebfd4ae4ba72d27db4b92d3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs new file mode 100644 index 00000000..08f3267d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public static class NameMakerFactory + { + public static INameMaker CreateDebugNameMaker() + { + return new DebugNameMaker(); + } + + public static INameMaker CreateNameMakerBaseASCIICharSet(string namePrefix) + { + var words = new List(); + for (int i = 0; i < 26; i++) + { + words.Add(((char)('a' + i)).ToString()); + words.Add(((char)('A' + i)).ToString()); + } + return new WordSetNameMaker(namePrefix, words); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta new file mode 100644 index 00000000..e1fb22c4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameMakerFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afa0e87123ec9854b806098330c4980a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs new file mode 100644 index 00000000..189ccf03 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + + public class NameScope : NameScopeBase + { + private readonly string _namePrefix; + private readonly List _wordSet; + private int _nextIndex; + + public NameScope(string namePrefix, List wordSet) + { + _namePrefix = namePrefix; + _wordSet = wordSet; + _nextIndex = 0; + } + + protected override void BuildNewName(StringBuilder nameBuilder, string originalName, string lastName) + { + nameBuilder.Append(_namePrefix); + for (int i = _nextIndex++; ;) + { + nameBuilder.Append(_wordSet[i % _wordSet.Count]); + i = i / _wordSet.Count; + if (i == 0) + { + break; + } + } + + // keep generic type name pattern {name}`{n}, if not, il2cpp may raise exception in typeof(G) when G contains a field likes `T a`. + int index = originalName.LastIndexOf('`'); + if (index != -1) + { + nameBuilder.Append(originalName.Substring(index)); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta new file mode 100644 index 00000000..e2242370 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a35c5c4b49c98a84f94b690c26900c33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs new file mode 100644 index 00000000..daa8cc8b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Text; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + public abstract class NameScopeBase : INameScope + { + + private readonly Dictionary _nameMap = new Dictionary(); + + private readonly HashSet _preservedNames = new HashSet(); + + + public bool AddPreservedName(string name) + { + if (!string.IsNullOrEmpty(name)) + { + return _preservedNames.Add(name); + } + return false; + } + + public bool IsNamePreserved(string name) + { + return _preservedNames.Contains(name); + } + + + protected abstract void BuildNewName(StringBuilder nameBuilder, string originalName, string lastName); + + private string CreateNewName(string originalName) + { + var nameBuilder = new StringBuilder(); + string lastName = null; + while (true) + { + nameBuilder.Clear(); + BuildNewName(nameBuilder, originalName, lastName); + string newName = nameBuilder.ToString(); + lastName = newName; + if (_preservedNames.Add(newName)) + { + return newName; + } + } + } + + public string GetNewName(string originalName, bool reuse) + { + if (!reuse) + { + return CreateNewName(originalName); + } + if (_nameMap.TryGetValue(originalName, out var newName)) + { + return newName; + } + newName = CreateNewName(originalName); + _nameMap[originalName] = newName; + return newName; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta new file mode 100644 index 00000000..17853c8a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/NameScopeBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26e6ae1f35e7f094c844cf1567b88a19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs new file mode 100644 index 00000000..f6edddb1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.SymbolObfus.NameMakers +{ + + public class WordSetNameMaker : NameMakerBase + { + private readonly string _namePrefix; + private readonly List _wordSet; + + public WordSetNameMaker(string namePrefix, List wordSet) + { + _namePrefix = namePrefix; + _wordSet = wordSet; + } + + protected override INameScope CreateNameScope() + { + return new NameScope(_namePrefix, _wordSet); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta new file mode 100644 index 00000000..23706b3d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/NameMakers/WordSetNameMaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47c92aea40a66e34b92f9eb5c0d380ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta new file mode 100644 index 00000000..c16875c9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 98e496436c90c0a4f82711af059471c7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs new file mode 100644 index 00000000..7b84d727 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs @@ -0,0 +1,67 @@ +using dnlib.DotNet; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class CacheRenamePolicy : ObfuscationPolicyBase + { + private readonly IObfuscationPolicy _underlyingPolicy; + + private readonly Dictionary _computeCache = new Dictionary(); + + public CacheRenamePolicy(IObfuscationPolicy underlyingPolicy) + { + _underlyingPolicy = underlyingPolicy; + } + + public override bool NeedRename(TypeDef typeDef) + { + if (!_computeCache.TryGetValue(typeDef, out var value)) + { + value = _underlyingPolicy.NeedRename(typeDef); + _computeCache[typeDef] = value; + } + return value; + } + + public override bool NeedRename(MethodDef methodDef) + { + if (!_computeCache.TryGetValue(methodDef, out var value)) + { + value = _underlyingPolicy.NeedRename(methodDef); + _computeCache[methodDef] = value; + } + return value; + } + + public override bool NeedRename(FieldDef fieldDef) + { + if (!_computeCache.TryGetValue(fieldDef, out var value)) + { + value = _underlyingPolicy.NeedRename(fieldDef); + _computeCache[fieldDef] = value; + } + return value; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + if (!_computeCache.TryGetValue(propertyDef, out var value)) + { + value = _underlyingPolicy.NeedRename(propertyDef); + _computeCache[propertyDef] = value; + } + return value; + } + + public override bool NeedRename(EventDef eventDef) + { + if (!_computeCache.TryGetValue(eventDef, out var value)) + { + value = _underlyingPolicy.NeedRename(eventDef); + _computeCache[eventDef] = value; + } + return value; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta new file mode 100644 index 00000000..ab4330f7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CacheRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c319b2ad62ad8794f9a8bc234c856d7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs new file mode 100644 index 00000000..a84a5877 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs @@ -0,0 +1,40 @@ +using dnlib.DotNet; +using System.Linq; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class CombineRenamePolicy : IObfuscationPolicy + { + private readonly IObfuscationPolicy[] _policies; + + public CombineRenamePolicy(params IObfuscationPolicy[] policies) + { + _policies = policies; + } + + public bool NeedRename(TypeDef typeDef) + { + return _policies.All(policy => policy.NeedRename(typeDef)); + } + + public bool NeedRename(MethodDef methodDef) + { + return _policies.All(policy => policy.NeedRename(methodDef)); + } + + public bool NeedRename(FieldDef fieldDef) + { + return _policies.All(policy => policy.NeedRename(fieldDef)); + } + + public bool NeedRename(PropertyDef propertyDef) + { + return _policies.All(policy => policy.NeedRename(propertyDef)); + } + + public bool NeedRename(EventDef eventDef) + { + return _policies.All(policy => policy.NeedRename(eventDef)); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta new file mode 100644 index 00000000..fcf6ea9d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/CombineRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97be4546adeb71947bf644949c3a9e82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs new file mode 100644 index 00000000..955c30d0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs @@ -0,0 +1,792 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using UnityEngine; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + + public class ConfigurableRenamePolicy : ObfuscationPolicyBase + { + enum ModifierType + { + None = 0x0, + Private = 0x1, + Protected = 0x2, + Public = 0x3, + } + + class MethodRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + } + + class FieldRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + } + + class PropertyRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + public ObfuzScope? applyToMembers; + } + + class EventRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public bool? obfuscateName; + public ObfuzScope? applyToMembers; + } + + class TypeRuleSpec + { + public NameMatcher nameMatcher; + public ModifierType? modifierType; + public ClassType? classType; + public List inheritTypes; + public List hasCustomAttributes; + public bool? obfuscateName; + public ObfuzScope? applyToMembers; + public bool applyToNestedTypes; + + public List fields; + public List methods; + public List properties; + public List events; + } + + class AssemblyRuleSpec + { + public string assemblyName; + public List types; + } + + private readonly Dictionary> _assemblyRuleSpecs = new Dictionary>(); + + private AssemblyRuleSpec ParseAssembly(XmlElement ele) + { + string assemblyName = ele.GetAttribute("name"); + if (string.IsNullOrEmpty(assemblyName)) + { + throw new Exception($"Invalid xml file, assembly name is empty"); + } + if (!_obfuscationAssemblyNames.Contains(assemblyName)) + { + throw new Exception($"unknown assembly name:{assemblyName}, not in ObfuzSettings.obfuscationAssemblyNames"); + } + var rule = new AssemblyRuleSpec() + { + assemblyName = assemblyName, + types = new List(), + }; + + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement childElement)) + { + continue; + } + if (childElement.Name != "type") + { + throw new Exception($"Invalid xml file, unknown node {childElement.Name}"); + } + TypeRuleSpec type = ParseType(childElement); + rule.types.Add(type); + } + return rule; + } + + private enum ClassType + { + None = 0x0, + Class = 0x1, + Struct = 0x2, + Interface = 0x4, + Enum = 0x8, + Delegate = 0x10, + } + + private ClassType? ParseClassType(string classType) + { + if (string.IsNullOrEmpty(classType)) + { + return null; + } + + ClassType type = ClassType.None; + foreach (var s in classType.Split(',')) + { + switch (s) + { + case "class": type |= ClassType.Class; break; + case "struct": type |= ClassType.Struct; break; + case "interface": type |= ClassType.Interface; break; + case "enum": type |= ClassType.Enum; break; + case "delegate": type |= ClassType.Delegate; break; + default: throw new Exception($"Invalid class type {s}"); + } + } + return type; + } + + private ModifierType? ParseModifierType(string modifierType) + { + if (string.IsNullOrEmpty(modifierType)) + { + return null; + } + ModifierType type = ModifierType.None; + foreach (var s in modifierType.Split(',')) + { + switch (s) + { + case "public": type |= ModifierType.Public; break; + case "protected": type |= ModifierType.Protected; break; + case "private": type |= ModifierType.Private; break; + default: throw new Exception($"Invalid modifier type {s}"); + } + } + return type; + } + + + private ObfuzScope? ParseApplyToMembersScope(string membersScopeStr) + { + if (string.IsNullOrWhiteSpace(membersScopeStr)) + { + return null; + } + ObfuzScope scope = ObfuzScope.None; + + foreach (string s in membersScopeStr.Split(',')) + { + var s2 = s.Trim().ToLowerInvariant(); + switch (s2) + { + case "none": break; + case "field": scope |= ObfuzScope.Field; break; + case "eventname": scope |= ObfuzScope.EventName; break; + case "eventaddremovefirename": scope |= ObfuzScope.EventAddRemoveFireName; break; + case "event": scope |= ObfuzScope.Event; break; + case "methodname": scope |= ObfuzScope.MethodName; break; + case "method": scope |= ObfuzScope.MethodName; break; + case "propertyname": scope |= ObfuzScope.PropertyName; break; + case "propertygettersettername": scope |= ObfuzScope.PropertyGetterSetterName; break; + case "property": scope |= ObfuzScope.Property; break; + case "all": + case "*": scope |= ObfuzScope.All; break; + default: + { + throw new Exception($"Invalid applyToMembers scope {s2}"); + } + } + } + + return scope; + } + + private List ParseTypes(string inheritStr) + { + if (string.IsNullOrWhiteSpace(inheritStr)) + { + return null; + } + var inheritTypes = new List(); + foreach (var s in inheritStr.Split(',')) + { + var trimmed = s.Trim(); + if (!string.IsNullOrEmpty(trimmed)) + { + inheritTypes.Add(trimmed); + } + } + return inheritTypes; + } + + private TypeRuleSpec ParseType(XmlElement element) + { + var rule = new TypeRuleSpec(); + + rule.nameMatcher = new NameMatcher(element.GetAttribute("name")); + rule.obfuscateName = ConfigUtil.ParseNullableBool(element.GetAttribute("obName")); + rule.applyToMembers = ParseApplyToMembersScope(element.GetAttribute("applyToMembers")); + rule.applyToNestedTypes = ConfigUtil.ParseNullableBool(element.GetAttribute("applyToNestedTypes")) ?? true; + rule.modifierType = ParseModifierType(element.GetAttribute("modifier")); + rule.classType = ParseClassType(element.GetAttribute("classType")); + rule.inheritTypes = ParseTypes(element.GetAttribute("inherit")); + rule.hasCustomAttributes = ParseTypes(element.GetAttribute("hasCustomAttributes")); + + //rule.nestTypeRuleSpecs = new List(); + rule.fields = new List(); + rule.methods = new List(); + rule.properties = new List(); + rule.events = new List(); + foreach (XmlNode node in element.ChildNodes) + { + if (!(node is XmlElement childElement)) + { + continue; + } + switch (childElement.Name) + { + case "field": + { + var fieldRuleSpec = new FieldRuleSpec(); + fieldRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + fieldRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + fieldRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + rule.fields.Add(fieldRuleSpec); + break; + } + case "method": + { + var methodRuleSpec = new MethodRuleSpec(); + methodRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + methodRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + methodRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + rule.methods.Add(methodRuleSpec); + break; + } + case "property": + { + var propertyRulerSpec = new PropertyRuleSpec(); + propertyRulerSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + propertyRulerSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + propertyRulerSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + propertyRulerSpec.applyToMembers = ParseApplyToMembersScope(childElement.GetAttribute("applyToMembers")); + rule.properties.Add(propertyRulerSpec); + break; + } + case "event": + { + var eventRuleSpec = new EventRuleSpec(); + eventRuleSpec.nameMatcher = new NameMatcher(childElement.GetAttribute("name")); + eventRuleSpec.modifierType = ParseModifierType(childElement.GetAttribute("modifier")); + eventRuleSpec.obfuscateName = ConfigUtil.ParseNullableBool(childElement.GetAttribute("obName")); + eventRuleSpec.applyToMembers = ParseApplyToMembersScope(childElement.GetAttribute("applyToMembers")); + rule.events.Add(eventRuleSpec); + break; + } + default: throw new Exception($"Invalid xml file, unknown node {childElement.Name} in type node"); + } + } + return rule; + } + + private void LoadXmls(List xmlFiles) + { + var rawAssemblySpecElements = new List(); + foreach (string file in xmlFiles) + { + LoadRawXml(file, rawAssemblySpecElements); + } + ResolveAssemblySpecs(rawAssemblySpecElements); + } + + private void ResolveAssemblySpecs(List rawAssemblySpecElements) + { + foreach (XmlElement ele in rawAssemblySpecElements) + { + var assemblyRule = ParseAssembly(ele); + if (!_assemblyRuleSpecs.TryGetValue(assemblyRule.assemblyName, out var existAssemblyRules)) + { + existAssemblyRules = new List(); + _assemblyRuleSpecs.Add(assemblyRule.assemblyName, existAssemblyRules); + } + existAssemblyRules.Add(assemblyRule); + } + } + + private void LoadRawXml(string xmlFile, List rawAssemblyElements) + { + Debug.Log($"ObfuscateRule::LoadXml {xmlFile}"); + var doc = new XmlDocument(); + doc.Load(xmlFile); + var root = doc.DocumentElement; + if (root.Name != "obfuz") + { + throw new Exception($"Invalid xml file {xmlFile}, root name should be 'obfuz'"); + } + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + switch (element.Name) + { + case "assembly": + { + rawAssemblyElements.Add(element); + break; + } + default: + { + throw new Exception($"Invalid xml file {xmlFile}, unknown node {element.Name}"); + } + } + } + } + + private ModifierType ComputeModifierType(TypeAttributes visibility) + { + if (visibility == TypeAttributes.NotPublic || visibility == TypeAttributes.NestedPrivate) + { + return ModifierType.Private; + } + if (visibility == TypeAttributes.Public || visibility == TypeAttributes.NestedPublic) + { + return ModifierType.Public; + } + return ModifierType.Protected; + } + + private ModifierType ComputeModifierType(FieldAttributes access) + { + if (access == FieldAttributes.Private || access == FieldAttributes.PrivateScope) + { + return ModifierType.Private; + } + if (access == FieldAttributes.Public) + { + return ModifierType.Public; + } + return ModifierType.Protected; + } + + //private ModifierType ComputeModifierType(MethodAttributes access) + //{ + // if (access == MethodAttributes.Private || access == MethodAttributes.PrivateScope) + // { + // return ModifierType.Private; + // } + // if (access == MethodAttributes.Public) + // { + // return ModifierType.Public; + // } + // return ModifierType.Protected; + //} + + private bool MatchModifier(ModifierType? modifierType, TypeDef typeDef) + { + return modifierType == null || (modifierType & ComputeModifierType(typeDef.Visibility)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, FieldDef fieldDef) + { + return modifierType == null || (modifierType & ComputeModifierType(fieldDef.Access)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, MethodDef methodDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)methodDef.Access)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, PropertyDef propertyDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)propertyDef.Attributes)) != 0; + } + + private bool MatchModifier(ModifierType? modifierType, EventDef eventDef) + { + return modifierType == null || (modifierType & ComputeModifierType((FieldAttributes)eventDef.Attributes)) != 0; + } + + private class MethodComputeCache + { + public bool obfuscateName = true; + public bool obfuscateParam = true; + public bool obfuscateBody = true; + } + + private class RuleResult + { + public bool? obfuscateName; + } + + private readonly Dictionary _typeSpecCache = new Dictionary(); + private readonly Dictionary _methodSpecCache = new Dictionary(); + private readonly Dictionary _fieldSpecCache = new Dictionary(); + private readonly Dictionary _propertySpecCache = new Dictionary(); + private readonly Dictionary _eventSpecCache = new Dictionary(); + + + private readonly HashSet _obfuscationAssemblyNames; + private readonly List _assembliesToObfuscate; + + public ConfigurableRenamePolicy(List obfuscationAssemblyNames, List assembliesToObfuscate, List xmlFiles) + { + _obfuscationAssemblyNames = new HashSet(obfuscationAssemblyNames); + _assembliesToObfuscate = assembliesToObfuscate; + LoadXmls(xmlFiles); + BuildRuleResultCaches(); + } + + private bool MatchClassType(ClassType? classType, TypeDef typeDef) + { + if (classType == null) + { + return true; + } + if (typeDef.IsInterface && (classType & ClassType.Interface) != 0) + { + return true; + } + if (typeDef.IsEnum && (classType & ClassType.Enum) != 0) + { + return true; + } + if (typeDef.IsDelegate && (classType & ClassType.Delegate) != 0) + { + return true; + } + if (typeDef.IsValueType && !typeDef.IsEnum && (classType & ClassType.Struct) != 0) + { + return true; + } + if (!typeDef.IsValueType && !typeDef.IsInterface && !typeDef.IsDelegate && (classType & ClassType.Class) != 0) + { + return true; + } + return false; + } + + + private RuleResult GetOrCreateTypeRuleResult(TypeDef typeDef) + { + if (!_typeSpecCache.TryGetValue(typeDef, out var ruleResult)) + { + ruleResult = new RuleResult(); + _typeSpecCache.Add(typeDef, ruleResult); + } + return ruleResult; + } + + private RuleResult GetOrCreateFieldRuleResult(FieldDef field) + { + if (!_fieldSpecCache.TryGetValue(field, out var ruleResult)) + { + ruleResult = new RuleResult(); + _fieldSpecCache.Add(field, ruleResult); + } + return ruleResult; + } + + private RuleResult GetOrCreateMethodRuleResult(MethodDef method) + { + if (!_methodSpecCache.TryGetValue(method, out var ruleResult)) + { + ruleResult = new RuleResult(); + _methodSpecCache.Add(method, ruleResult); + } + return ruleResult; + } + + private RuleResult GetOrCreatePropertyRuleResult(PropertyDef property) + { + if (!_propertySpecCache.TryGetValue(property, out var ruleResult)) + { + ruleResult = new RuleResult(); + _propertySpecCache.Add(property, ruleResult); + } + return ruleResult; + } + + private RuleResult GetOrCreateEventRuleResult(EventDef eventDef) + { + if (!_eventSpecCache.TryGetValue(eventDef, out var ruleResult)) + { + ruleResult = new RuleResult(); + _eventSpecCache.Add(eventDef, ruleResult); + } + return ruleResult; + } + + private void BuildTypeRuleResult(TypeRuleSpec typeSpec, TypeDef typeDef, RuleResult typeRuleResult) + { + string typeName = typeDef.FullName; + + if (typeSpec.obfuscateName != null) + { + typeRuleResult.obfuscateName = typeSpec.obfuscateName; + } + + foreach (var fieldDef in typeDef.Fields) + { + RuleResult fieldRuleResult = GetOrCreateFieldRuleResult(fieldDef); + if (typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.Field) != 0 && typeSpec.obfuscateName != null) + { + fieldRuleResult.obfuscateName = typeSpec.obfuscateName; + } + foreach (var fieldSpec in typeSpec.fields) + { + if (fieldSpec.nameMatcher.IsMatch(fieldDef.Name) && MatchModifier(fieldSpec.modifierType, fieldDef)) + { + if (fieldSpec.obfuscateName != null) + { + fieldRuleResult.obfuscateName = fieldSpec.obfuscateName; + } + } + } + } + + foreach (MethodDef methodDef in typeDef.Methods) + { + RuleResult methodRuleResult = GetOrCreateMethodRuleResult(methodDef); + if (typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.Method) != 0 && typeSpec.obfuscateName != null) + { + methodRuleResult.obfuscateName = typeSpec.obfuscateName; + } + } + + foreach (var eventDef in typeDef.Events) + { + RuleResult eventRuleResult = GetOrCreateEventRuleResult(eventDef); + if (typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.EventName) != 0 && typeSpec.obfuscateName != null) + { + eventRuleResult.obfuscateName = typeSpec.obfuscateName; + } + foreach (var eventSpec in typeSpec.events) + { + if (!eventSpec.nameMatcher.IsMatch(eventDef.Name) || !MatchModifier(eventSpec.modifierType, eventDef)) + { + continue; + } + if (typeSpec.obfuscateName != null && typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.EventAddRemoveFireName) != 0) + { + if (eventDef.AddMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.AddMethod).obfuscateName = typeSpec.obfuscateName; + } + if (eventDef.RemoveMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.RemoveMethod).obfuscateName = typeSpec.obfuscateName; + } + if (eventDef.InvokeMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.InvokeMethod).obfuscateName = typeSpec.obfuscateName; + } + } + if (eventSpec.obfuscateName != null) + { + eventRuleResult.obfuscateName = eventSpec.obfuscateName; + if (eventSpec.applyToMembers != null && (eventSpec.applyToMembers & ObfuzScope.EventAddRemoveFireName) != 0) + { + if (eventDef.AddMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.AddMethod).obfuscateName = eventSpec.obfuscateName; + } + if (eventDef.RemoveMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.RemoveMethod).obfuscateName = eventSpec.obfuscateName; + } + if (eventDef.InvokeMethod != null) + { + GetOrCreateMethodRuleResult(eventDef.InvokeMethod).obfuscateName = eventSpec.obfuscateName; + } + } + } + } + } + + foreach (var propertyDef in typeDef.Properties) + { + RuleResult propertyRuleResult = GetOrCreatePropertyRuleResult(propertyDef); + if (typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.PropertyName) != 0 && typeSpec.obfuscateName != null) + { + propertyRuleResult.obfuscateName = typeSpec.obfuscateName; + } + foreach (var propertySpec in typeSpec.properties) + { + if (!propertySpec.nameMatcher.IsMatch(propertyDef.Name) || !MatchModifier(propertySpec.modifierType, propertyDef)) + { + continue; + } + if (typeSpec.obfuscateName != null && typeSpec.applyToMembers != null && (typeSpec.applyToMembers & ObfuzScope.PropertyGetterSetterName) != 0) + { + if (propertyDef.GetMethod != null) + { + GetOrCreateMethodRuleResult(propertyDef.GetMethod).obfuscateName = typeSpec.obfuscateName; + } + if (propertyDef.SetMethod != null) + { + GetOrCreateMethodRuleResult(propertyDef.SetMethod).obfuscateName = typeSpec.obfuscateName; + } + } + if (propertySpec.obfuscateName != null) + { + propertyRuleResult.obfuscateName = propertySpec.obfuscateName; + if (propertySpec.applyToMembers != null && (propertySpec.applyToMembers & ObfuzScope.PropertyGetterSetterName) != 0) + { + if (propertyDef.GetMethod != null) + { + GetOrCreateMethodRuleResult(propertyDef.GetMethod).obfuscateName = propertySpec.obfuscateName; + } + if (propertyDef.SetMethod != null) + { + GetOrCreateMethodRuleResult(propertyDef.SetMethod).obfuscateName = propertySpec.obfuscateName; + } + } + } + } + } + foreach (MethodDef methodDef in typeDef.Methods) + { + RuleResult methodRuleResult = GetOrCreateMethodRuleResult(methodDef); + foreach (MethodRuleSpec methodSpec in typeSpec.methods) + { + if (!methodSpec.nameMatcher.IsMatch(methodDef.Name) || !MatchModifier(methodSpec.modifierType, methodDef)) + { + continue; + } + if (methodSpec.obfuscateName != null) + { + methodRuleResult.obfuscateName = methodSpec.obfuscateName; + } + } + } + + if (typeSpec.applyToNestedTypes) + { + foreach (TypeDef nestedType in typeDef.NestedTypes) + { + var nestedRuleResult = GetOrCreateTypeRuleResult(nestedType); + BuildTypeRuleResult(typeSpec, nestedType, nestedRuleResult); + } + } + } + + private bool MatchInheritTypes(List inheritTypes, TypeDef typeDef) + { + if (inheritTypes == null || inheritTypes.Count == 0) + { + return true; + } + TypeDef currentType = typeDef; + while (currentType != null) + { + if (inheritTypes.Contains(currentType.FullName)) + { + return true; + } + foreach (var interfaceType in currentType.Interfaces) + { + if (inheritTypes.Contains(interfaceType.Interface.FullName)) + { + return true; + } + } + currentType = MetaUtil.GetBaseTypeDef(currentType); + } + return false; + } + + private bool MatchCustomAttributes(List customAttributes, TypeDef typeDef) + { + if (customAttributes == null || customAttributes.Count == 0) + { + return true; + } + foreach (string customAttributeName in customAttributes) + { + if (typeDef.CustomAttributes.Find(customAttributeName) != null) + { + return true; + } + } + return false; + } + + private IEnumerable GetMatchTypes(ModuleDef mod, List types, TypeRuleSpec typeSpec) + { + if (typeSpec.nameMatcher.IsWildcardPattern) + { + foreach (var typeDef in types) + { + if (!typeSpec.nameMatcher.IsMatch(typeDef.FullName) + || !MatchModifier(typeSpec.modifierType, typeDef) + || !MatchClassType(typeSpec.classType, typeDef) + || !MatchInheritTypes(typeSpec.inheritTypes, typeDef) + || !MatchCustomAttributes(typeSpec.hasCustomAttributes, typeDef)) + { + continue; + } + yield return typeDef; + } + } + else + { + TypeDef typeDef = mod.FindNormal(typeSpec.nameMatcher.NameOrPattern); + if (typeDef != null + && MatchModifier(typeSpec.modifierType, typeDef) + && MatchClassType(typeSpec.classType, typeDef) + && MatchInheritTypes(typeSpec.inheritTypes, typeDef) + && MatchCustomAttributes(typeSpec.hasCustomAttributes, typeDef)) + { + yield return typeDef; + } + } + } + + private void BuildRuleResultCaches() + { + foreach (AssemblyRuleSpec assSpec in _assemblyRuleSpecs.Values.SelectMany(arr => arr)) + { + ModuleDef module = _assembliesToObfuscate.FirstOrDefault(m => m.Assembly.Name == assSpec.assemblyName); + if (module == null) + { + continue; + } + List types = module.GetTypes().ToList(); + foreach (TypeRuleSpec typeSpec in assSpec.types) + { + foreach (var typeDef in GetMatchTypes(module, types, typeSpec)) + { + var ruleResult = GetOrCreateTypeRuleResult(typeDef); + if (typeSpec.obfuscateName != null) + { + ruleResult.obfuscateName = typeSpec.obfuscateName; + } + BuildTypeRuleResult(typeSpec, typeDef, ruleResult); + } + } + } + } + + public override bool NeedRename(TypeDef typeDef) + { + return GetOrCreateTypeRuleResult(typeDef).obfuscateName != false; + } + + public override bool NeedRename(MethodDef methodDef) + { + return GetOrCreateMethodRuleResult(methodDef).obfuscateName != false; + } + + public override bool NeedRename(FieldDef fieldDef) + { + return GetOrCreateFieldRuleResult(fieldDef).obfuscateName != false; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + return GetOrCreatePropertyRuleResult(propertyDef).obfuscateName != false; + } + + public override bool NeedRename(EventDef eventDef) + { + return GetOrCreateEventRuleResult(eventDef).obfuscateName != false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta new file mode 100644 index 00000000..e6b4feea --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ConfigurableRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c063bc949939fe44972c3d99870527e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs new file mode 100644 index 00000000..6a271ad0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs @@ -0,0 +1,33 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + + public virtual bool NeedRename(TypeDef typeDef) + { + return true; + } + + public virtual bool NeedRename(MethodDef methodDef) + { + return true; + } + + public virtual bool NeedRename(FieldDef fieldDef) + { + return true; + } + + public virtual bool NeedRename(PropertyDef propertyDef) + { + return true; + } + + public virtual bool NeedRename(EventDef eventDef) + { + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta new file mode 100644 index 00000000..2d0bf054 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/ObfuscationPolicyBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ab98107a2ef9624b9b8a53061f682c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs new file mode 100644 index 00000000..132ed15a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs @@ -0,0 +1,45 @@ +using dnlib.DotNet; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + internal class SupportPassPolicy : ObfuscationPolicyBase + { + private readonly ConfigurablePassPolicy _policy; + + + private bool Support(ObfuscationPassType passType) + { + return passType.HasFlag(ObfuscationPassType.SymbolObfus); + } + + public SupportPassPolicy(ConfigurablePassPolicy policy) + { + _policy = policy; + } + + public override bool NeedRename(TypeDef typeDef) + { + return Support(_policy.GetTypeObfuscationPasses(typeDef)); + } + + public override bool NeedRename(MethodDef methodDef) + { + return Support(_policy.GetMethodObfuscationPasses(methodDef)); + } + + public override bool NeedRename(FieldDef fieldDef) + { + return Support(_policy.GetFieldObfuscationPasses(fieldDef)); + } + + public override bool NeedRename(PropertyDef propertyDef) + { + return Support(_policy.GetPropertyObfuscationPasses(propertyDef)); + } + + public override bool NeedRename(EventDef eventDef) + { + return Support(_policy.GetEventObfuscationPasses(eventDef)); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta new file mode 100644 index 00000000..bf7d3b8d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SupportPassPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 584dd4d4e9b9fa64090611d84b50980b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs new file mode 100644 index 00000000..68d4ea2f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs @@ -0,0 +1,120 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + public class SystemRenamePolicy : ObfuscationPolicyBase + { + private readonly ObfuzIgnoreScopeComputeCache _obfuzIgnoreScopeComputeCache; + + public SystemRenamePolicy(ObfuzIgnoreScopeComputeCache obfuzIgnoreScopeComputeCache) + { + _obfuzIgnoreScopeComputeCache = obfuzIgnoreScopeComputeCache; + } + + private readonly HashSet _fullIgnoreTypeFullNames = new HashSet + { + ConstValues.ObfuzIgnoreAttributeFullName, + ConstValues.ObfuzScopeFullName, + ConstValues.EncryptFieldAttributeFullName, + ConstValues.EmbeddedAttributeFullName, + ConstValues.ZluaLuaInvokeAttributeFullName, + ConstValues.ZluaLuaCallbackAttributeFullName, + ConstValues.ZluaLuaMarshalAsAttributeFullName, + ConstValues.BurstCompileFullName, + }; + + + private readonly HashSet _fullIgnoreTypeNames = new HashSet + { + ConstValues.MonoPInvokeCallbackAttributeName, + }; + + private bool IsFullIgnoreObfuscatedType(TypeDef typeDef) + { + return _fullIgnoreTypeFullNames.Contains(typeDef.FullName) || _fullIgnoreTypeNames.Contains(typeDef.Name) || MetaUtil.HasMicrosoftCodeAnalysisEmbeddedAttribute(typeDef); + } + + public override bool NeedRename(TypeDef typeDef) + { + string name = typeDef.Name; + if (name == "") + { + return false; + } + if (IsFullIgnoreObfuscatedType(typeDef)) + { + return false; + } + + if (_obfuzIgnoreScopeComputeCache.HasSelfOrEnclosingOrInheritObfuzIgnoreScope(typeDef, ObfuzScope.TypeName)) + { + return false; + } + return true; + } + + public override bool NeedRename(MethodDef methodDef) + { + if (methodDef.DeclaringType.IsDelegate || IsFullIgnoreObfuscatedType(methodDef.DeclaringType)) + { + return false; + } + if (methodDef.Name == ".ctor" || methodDef.Name == ".cctor") + { + return false; + } + + if (_obfuzIgnoreScopeComputeCache.HasSelfOrInheritPropertyOrEventOrOrTypeDefIgnoreMethodName(methodDef)) + { + return false; + } + return true; + } + + public override bool NeedRename(FieldDef fieldDef) + { + if (fieldDef.DeclaringType.IsDelegate || IsFullIgnoreObfuscatedType(fieldDef.DeclaringType)) + { + return false; + } + if (_obfuzIgnoreScopeComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(fieldDef, fieldDef.DeclaringType, ObfuzScope.Field)) + { + return false; + } + if (fieldDef.DeclaringType.IsEnum && !fieldDef.IsStatic) + { + return false; + } + return true; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + if (propertyDef.DeclaringType.IsDelegate || IsFullIgnoreObfuscatedType(propertyDef.DeclaringType)) + { + return false; + } + if (_obfuzIgnoreScopeComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(propertyDef, propertyDef.DeclaringType, ObfuzScope.PropertyName)) + { + return false; + } + return true; + } + + public override bool NeedRename(EventDef eventDef) + { + if (eventDef.DeclaringType.IsDelegate || IsFullIgnoreObfuscatedType(eventDef.DeclaringType)) + { + return false; + } + if (_obfuzIgnoreScopeComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(eventDef, eventDef.DeclaringType, ObfuzScope.EventName)) + { + return false; + } + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta new file mode 100644 index 00000000..b7d45e3e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/SystemRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b7b98f2ff075c04aa9bd989f8797f00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs new file mode 100644 index 00000000..594c5a1c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs @@ -0,0 +1,264 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.ObfusPasses.SymbolObfus.Policies +{ + + public class UnityRenamePolicy : ObfuscationPolicyBase + { + private static HashSet s_monoBehaviourEvents = new HashSet { + + // MonoBehaviour events + "Awake", + "FixedUpdate", + "LateUpdate", + "OnAnimatorIK", + + "OnAnimatorMove", + "OnApplicationFocus", + "OnApplicationPause", + "OnApplicationQuit", + "OnAudioFilterRead", + + "OnBecameVisible", + "OnBecameInvisible", + + "OnCollisionEnter", + "OnCollisionEnter2D", + "OnCollisionExit", + "OnCollisionExit2D", + "OnCollisionStay", + "OnCollisionStay2D", + "OnConnectedToServer", + "OnControllerColliderHit", + + "OnDrawGizmos", + "OnDrawGizmosSelected", + "OnDestroy", + "OnDisable", + "OnDisconnectedFromServer", + + "OnEnable", + + "OnFailedToConnect", + "OnFailedToConnectToMasterServer", + + "OnGUI", + + "OnJointBreak", + "OnJointBreak2D", + + "OnMasterServerEvent", + "OnMouseDown", + "OnMouseDrag", + "OnMouseEnter", + "OnMouseExit", + "OnMouseOver", + "OnMouseUp", + "OnMouseUpAsButton", + + "OnNetworkInstantiate", + + "OnParticleSystemStopped", + "OnParticleTrigger", + "OnParticleUpdateJobScheduled", + "OnPlayerConnected", + "OnPlayerDisconnected", + "OnPostRender", + "OnPreCull", + "OnPreRender", + "OnRenderImage", + "OnRenderObject", + + "OnSerializeNetworkView", + "OnServerInitialized", + + "OnTransformChildrenChanged", + "OnTransformParentChanged", + "OnTriggerEnter", + "OnTriggerEnter2D", + "OnTriggerExit", + "OnTriggerExit2D", + "OnTriggerStay", + "OnTriggerStay2D", + + "OnValidate", + "OnWillRenderObject", + "Reset", + "Start", + "Update", + + // Animator/StateMachineBehaviour + "OnStateEnter", + "OnStateExit", + "OnStateMove", + "OnStateUpdate", + "OnStateIK", + "OnStateMachineEnter", + "OnStateMachineExit", + + // ParticleSystem + "OnParticleTrigger", + "OnParticleCollision", + "OnParticleSystemStopped", + + // UGUI/EventSystems + "OnPointerClick", + "OnPointerDown", + "OnPointerUp", + "OnPointerEnter", + "OnPointerExit", + "OnDrag", + "OnBeginDrag", + "OnEndDrag", + "OnDrop", + "OnScroll", + "OnSelect", + "OnDeselect", + "OnMove", + "OnSubmit", + "OnCancel", +}; + + private readonly CachedDictionary _computeDeclaringTypeDisableAllMemberRenamingCache; + private readonly CachedDictionary _isSerializableCache; + private readonly CachedDictionary _isInheritFromMonoBehaviourCache; + private readonly CachedDictionary _isScriptOrSerializableTypeCache; + + public UnityRenamePolicy() + { + _computeDeclaringTypeDisableAllMemberRenamingCache = new CachedDictionary(ComputeDeclaringTypeDisableAllMemberRenaming); + _isSerializableCache = new CachedDictionary(MetaUtil.IsSerializableType); + _isInheritFromMonoBehaviourCache = new CachedDictionary(MetaUtil.IsInheritFromMonoBehaviour); + _isScriptOrSerializableTypeCache = new CachedDictionary(MetaUtil.IsScriptOrSerializableType); + } + + private bool IsUnitySourceGeneratedAssemblyType(TypeDef typeDef) + { + if (typeDef.Name.StartsWith("UnitySourceGeneratedAssemblyMonoScriptTypes_")) + { + return true; + } + if (typeDef.FullName == "Unity.Entities.CodeGeneratedRegistry.AssemblyTypeRegistry") + { + return true; + } + if (typeDef.Name.StartsWith("__JobReflectionRegistrationOutput")) + { + return true; + } + if (MetaUtil.HasDOTSCompilerGeneratedAttribute(typeDef)) + { + return true; + } + if (typeDef.DeclaringType != null) + { + return IsUnitySourceGeneratedAssemblyType(typeDef.DeclaringType); + } + return false; + } + + private bool ComputeDeclaringTypeDisableAllMemberRenaming(TypeDef typeDef) + { + if (typeDef.IsEnum && MetaUtil.HasBlackboardEnumAttribute(typeDef)) + { + return true; + } + if (IsUnitySourceGeneratedAssemblyType(typeDef)) + { + return true; + } + if (MetaUtil.IsInheritFromDOTSTypes(typeDef)) + { + return true; + } + return false; + } + + public override bool NeedRename(TypeDef typeDef) + { + if (_isScriptOrSerializableTypeCache.GetValue(typeDef)) + { + return false; + } + if (_computeDeclaringTypeDisableAllMemberRenamingCache.GetValue(typeDef)) + { + return false; + } + if (MetaUtil.HasBurstCompileAttribute(typeDef)) + { + return false; + } + if (typeDef.Methods.Any(m => MetaUtil.HasRuntimeInitializeOnLoadMethodAttribute(m))) + { + return false; + } + return true; + } + + public override bool NeedRename(MethodDef methodDef) + { + TypeDef typeDef = methodDef.DeclaringType; + if (s_monoBehaviourEvents.Contains(methodDef.Name) && _isInheritFromMonoBehaviourCache.GetValue(typeDef)) + { + return false; + } + if (_computeDeclaringTypeDisableAllMemberRenamingCache.GetValue(typeDef)) + { + return false; + } + if (MetaUtil.HasRuntimeInitializeOnLoadMethodAttribute(methodDef)) + { + return false; + } + if (MetaUtil.HasBurstCompileAttribute(methodDef) || MetaUtil.HasBurstCompileAttribute(methodDef.DeclaringType) || MetaUtil.HasDOTSCompilerGeneratedAttribute(methodDef)) + { + return false; + } + return true; + } + + public override bool NeedRename(FieldDef fieldDef) + { + TypeDef typeDef = fieldDef.DeclaringType; + if (_isScriptOrSerializableTypeCache.GetValue(typeDef)) + { + if (typeDef.IsEnum) + { + return false; + } + if (fieldDef.IsPublic && !fieldDef.IsStatic) + { + return false; + } + if (!fieldDef.IsStatic && MetaUtil.IsSerializableField(fieldDef)) + { + return false; + } + } + if (_computeDeclaringTypeDisableAllMemberRenamingCache.GetValue(typeDef)) + { + return false; + } + return true; + } + + public override bool NeedRename(PropertyDef propertyDef) + { + TypeDef typeDef = propertyDef.DeclaringType; + if (_isSerializableCache.GetValue(typeDef)) + { + bool isGetterPublic = propertyDef.GetMethod != null && propertyDef.GetMethod.IsPublic && !propertyDef.GetMethod.IsStatic; + bool isSetterPublic = propertyDef.SetMethod != null && propertyDef.SetMethod.IsPublic && !propertyDef.SetMethod.IsStatic; + + if (isGetterPublic || isSetterPublic) + { + return false; + } + } + return true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta new file mode 100644 index 00000000..f8f6f99a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/Policies/UnityRenamePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4357f35c667599246b2481b093f41f04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs new file mode 100644 index 00000000..8d644bbe --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs @@ -0,0 +1,306 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public class ReflectionCompatibilityDetector + { + private readonly HashSet _assembliesToObfuscate; + private readonly List _obfuscatedAndNotObfuscatedModules; + private readonly IObfuscationPolicy _renamePolicy; + + public ReflectionCompatibilityDetector(List assembliesToObfuscate, List obfuscatedAndNotObfuscatedModules, IObfuscationPolicy renamePolicy) + { + _assembliesToObfuscate = new HashSet(assembliesToObfuscate); + _obfuscatedAndNotObfuscatedModules = obfuscatedAndNotObfuscatedModules; + _renamePolicy = renamePolicy; + } + + public void Analyze() + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + AnalyzeMethod(method); + } + } + } + } + + private MethodDef _curCallingMethod; + private IList _curInstructions; + private int _curInstIndex; + + private void AnalyzeMethod(MethodDef method) + { + if (!method.HasBody) + { + return; + } + _curCallingMethod = method; + _curInstructions = method.Body.Instructions; + _curInstIndex = 0; + for (int n = _curInstructions.Count; _curInstIndex < n; _curInstIndex++) + { + var inst = _curInstructions[_curInstIndex]; + switch (inst.OpCode.Code) + { + case Code.Call: + { + AnalyzeCall(inst.Operand as IMethod); + break; + } + case Code.Callvirt: + { + ITypeDefOrRef constrainedType = null; + if (_curInstIndex > 0) + { + var prevInst = _curInstructions[_curInstIndex - 1]; + if (prevInst.OpCode.Code == Code.Constrained) + { + constrainedType = prevInst.Operand as ITypeDefOrRef; + } + } + AnalyzeCallvir(inst.Operand as IMethod, constrainedType); + break; + } + } + } + } + + private ITypeDefOrRef FindLatestTypeOf(int backwardFindInstructionCount) + { + // find sequence ldtoken ; + for (int i = 2; i <= backwardFindInstructionCount; i++) + { + int index = _curInstIndex - i; + if (index < 0) + { + return null; + } + Instruction inst1 = _curInstructions[index]; + Instruction inst2 = _curInstructions[index + 1]; + if (inst1.OpCode.Code == Code.Ldtoken && inst2.OpCode.Code == Code.Call) + { + if (!(inst1.Operand is ITypeDefOrRef typeDefOrRef)) + { + continue; + } + IMethod method = inst2.Operand as IMethod; + if (method.Name == "GetTypeFromHandle" && method.DeclaringType.FullName == "System.Type") + { + // Ldtoken ; Call System.Type.GetTypeFromHandle(System.RuntimeTypeHandle handle) + return typeDefOrRef; + } + } + } + return null; + } + + private void AnalyzeCall(IMethod calledMethod) + { + TypeDef callType = calledMethod.DeclaringType.ResolveTypeDef(); + if (callType == null) + { + return; + } + switch (callType.FullName) + { + case "System.Enum": + { + AnalyzeEnum(calledMethod, callType); + break; + } + case "System.Type": + { + AnalyzeGetType(calledMethod, callType); + break; + } + case "System.Reflection.Assembly": + { + if (calledMethod.Name == "GetType") + { + AnalyzeGetType(calledMethod, callType); + } + break; + } + } + } + + + private bool IsAnyEnumItemRenamed(TypeDef typeDef) + { + return _assembliesToObfuscate.Contains(typeDef.Module) && typeDef.Fields.Any(f => _renamePolicy.NeedRename(f)); + } + + private void AnalyzeCallvir(IMethod calledMethod, ITypeDefOrRef constrainedType) + { + TypeDef callType = calledMethod.DeclaringType.ResolveTypeDef(); + if (callType == null) + { + return; + } + string calledMethodName = calledMethod.Name; + switch (callType.FullName) + { + case "System.Object": + { + if (calledMethodName == "ToString") + { + if (constrainedType != null) + { + TypeDef enumTypeDef = constrainedType.ResolveTypeDef(); + if (enumTypeDef != null && enumTypeDef.IsEnum && IsAnyEnumItemRenamed(enumTypeDef)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: {enumTypeDef.FullName}.ToString() the enum members are renamed."); + } + } + } + break; + } + case "System.Type": + { + AnalyzeGetType(calledMethod, callType); + break; + } + } + } + + private TypeSig GetMethodGenericParameter(IMethod method) + { + if (method is MethodSpec ms) + { + return ms.GenericInstMethodSig.GenericArguments.FirstOrDefault(); + } + else + { + return null; + } + } + + private void AnalyzeEnum(IMethod method, TypeDef typeDef) + { + const int extraSearchInstructionCount = 3; + TypeSig parseTypeSig = GetMethodGenericParameter(method); + TypeDef parseType = parseTypeSig?.ToTypeDefOrRef().ResolveTypeDef(); + switch (method.Name) + { + case "Parse": + { + if (parseTypeSig != null) + { + // Enum.Parse(string name) or Enum.Parse(string name, bool caseInsensitive) + if (parseType != null) + { + if (IsAnyEnumItemRenamed(parseType)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.Parse field of T:{parseType.FullName} is renamed."); + } + } + else + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.Parse field of T should not be renamed."); + } + } + else + { + // Enum.Parse(Type type, string name) or Enum.Parse(Type type, string name, bool ignoreCase) + TypeDef enumType = FindLatestTypeOf(method.GetParamCount() + extraSearchInstructionCount)?.ResolveTypeDef(); + if (enumType != null && enumType.IsEnum && IsAnyEnumItemRenamed(enumType)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.Parse field of argument type:{enumType.FullName} is renamed."); + } + else + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.Parse field of argument `type` should not be renamed."); + } + } + break; + } + case "TryParse": + { + if (parseTypeSig != null) + { + // Enum.TryParse(string name, out T result) or Enum.TryParse(string name, bool ignoreCase, out T result) + if (parseType != null) + { + if (IsAnyEnumItemRenamed(parseType)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.TryParse field of T:{parseType.FullName} is renamed."); + } + } + else + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.TryParse field of T should not be renamed."); + } + } + else + { + throw new Exception("impossible"); + } + break; + } + case "GetName": + { + // Enum.GetName(Type type, object value) + TypeDef enumType = FindLatestTypeOf(method.GetParamCount() + extraSearchInstructionCount)?.ResolveTypeDef(); + if (enumType != null && enumType.IsEnum && IsAnyEnumItemRenamed(enumType)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.GetName field of type:{enumType.FullName} is renamed."); + } + else + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.GetName field of argument `type` should not be renamed."); + } + break; + } + case "GetNames": + { + // Enum.GetNames(Type type) + TypeDef enumType = FindLatestTypeOf(method.GetParamCount() + extraSearchInstructionCount)?.ResolveTypeDef(); + if (enumType != null && enumType.IsEnum && IsAnyEnumItemRenamed(enumType)) + { + Debug.LogError($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.GetNames field of type:{enumType.FullName} is renamed."); + } + else + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Enum.GetNames field of argument `type` should not be renamed."); + } + break; + } + } + } + + private void AnalyzeGetType(IMethod method, TypeDef declaringType) + { + switch (method.Name) + { + case "GetType": + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: Type.GetType argument `typeName` should not be renamed."); + break; + } + case "GetField": + case "GetFields": + case "GetMethod": + case "GetMethods": + case "GetProperty": + case "GetProperties": + case "GetEvent": + case "GetEvents": + case "GetMembers": + { + Debug.LogWarning($"[ReflectionCompatibilityDetector] Reflection compatibility issue in {_curCallingMethod}: called method:{method} the members of type should not be renamed."); + break; + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs.meta new file mode 100644 index 00000000..f5212e11 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/ReflectionCompatibilityDetector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b98c97a4d1db5d945bce0fdd2bd09202 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs new file mode 100644 index 00000000..65486951 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs @@ -0,0 +1,742 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + + public class RenameRecordMap + { + private enum RenameStatus + { + NotRenamed, + Renamed, + } + + private class RenameRecord + { + public RenameStatus status; + public string signature; + public string oldName; + public string newName; + public string oldStackTraceSignature; // only for MethodDef + public object renameMappingData; + } + + private class RenameMappingField + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingMethod + { + public RenameStatus status; + public string signature; + public string newName; + public string oldStackTraceSignature; + public string newStackTraceSignature; + } + + private class RenameMappingMethodParam + { + public RenameStatus status; + public int index; + public string newName; + } + + private class RenameMappingProperty + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingEvent + { + public RenameStatus status; + public string signature; + public string newName; + } + + private class RenameMappingType + { + public RenameStatus status; + public string oldFullName; + public string newFullName; + + public Dictionary fields = new Dictionary(); + public Dictionary methods = new Dictionary(); + public Dictionary properties = new Dictionary(); + public Dictionary events = new Dictionary(); + } + + private class RenameMappingAssembly + { + public string assName; + + public Dictionary types = new Dictionary(); + } + + private readonly string _mappingFile; + private readonly bool _debug; + private readonly bool _keepUnknownSymbolInSymbolMappingFile; + private readonly Dictionary _assemblies = new Dictionary(); + + + private readonly Dictionary _modRenames = new Dictionary(); + private readonly Dictionary _typeRenames = new Dictionary(); + private readonly Dictionary _methodRenames = new Dictionary(); + private readonly Dictionary _fieldRenames = new Dictionary(); + private readonly Dictionary _propertyRenames = new Dictionary(); + private readonly Dictionary _eventRenames = new Dictionary(); + private readonly Dictionary _virtualMethodGroups = new Dictionary(); + + + public RenameRecordMap(string mappingFile, bool debug, bool keepUnknownSymbolInSymbolMappingFile) + { + _mappingFile = mappingFile; + _debug = debug; + _keepUnknownSymbolInSymbolMappingFile = keepUnknownSymbolInSymbolMappingFile; + } + + public void Init(List assemblies, INameMaker nameMaker) + { + LoadXmlMappingFile(_mappingFile); + foreach (ModuleDef mod in assemblies) + { + string name = mod.Assembly.Name; + + RenameMappingAssembly rma = _assemblies.GetValueOrDefault(name); + + _modRenames.Add(mod, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = name, + oldName = name, + newName = null, + renameMappingData = rma, + }); + + foreach (TypeDef type in mod.GetTypes()) + { + nameMaker.AddPreservedName(type, name); + nameMaker.AddPreservedNamespace(type, type.Namespace); + string fullTypeName = type.FullName; + RenameMappingType rmt = rma?.types.GetValueOrDefault(fullTypeName); + if (rmt != null && rmt.status == RenameStatus.Renamed) + { + var (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); + nameMaker.AddPreservedNamespace(type, newNamespace); + nameMaker.AddPreservedName(type, newName); + } + + _typeRenames.Add(type, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = fullTypeName, + oldName = fullTypeName, + newName = null, + renameMappingData = rmt, + }); + foreach (MethodDef method in type.Methods) + { + nameMaker.AddPreservedName(method, method.Name); + string methodSig = TypeSigUtil.ComputeMethodDefSignature(method); + + RenameMappingMethod rmm = rmt?.methods.GetValueOrDefault(methodSig); + if (rmm != null && rmm.status == RenameStatus.Renamed) + { + nameMaker.AddPreservedName(method, rmm.newName); + } + _methodRenames.Add(method, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = methodSig, + oldName = method.Name, + newName = null, + renameMappingData = rmm, + oldStackTraceSignature = MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method), + }); + } + foreach (FieldDef field in type.Fields) + { + nameMaker.AddPreservedName(field, field.Name); + string fieldSig = TypeSigUtil.ComputeFieldDefSignature(field); + RenameMappingField rmf = rmt?.fields.GetValueOrDefault(fieldSig); + if (rmf != null && rmf.status == RenameStatus.Renamed) + { + nameMaker.AddPreservedName(field, rmf.newName); + } + _fieldRenames.Add(field, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = fieldSig, + oldName = field.Name, + newName = null, + renameMappingData = rmf, + }); + } + foreach (PropertyDef property in type.Properties) + { + nameMaker.AddPreservedName(property, property.Name); + string propertySig = TypeSigUtil.ComputePropertyDefSignature(property); + RenameMappingProperty rmp = rmt?.properties.GetValueOrDefault(propertySig); + if (rmp != null && rmp.status == RenameStatus.Renamed) + { + nameMaker.AddPreservedName(property, rmp.newName); + } + _propertyRenames.Add(property, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = propertySig, + oldName = property.Name, + newName = null, + renameMappingData = rmp, + }); + } + foreach (EventDef eventDef in type.Events) + { + nameMaker.AddPreservedName(eventDef, eventDef.Name); + string eventSig = TypeSigUtil.ComputeEventDefSignature(eventDef); + RenameMappingEvent rme = rmt?.events.GetValueOrDefault(eventSig); + if (rme != null && rme.status == RenameStatus.Renamed) + { + nameMaker.AddPreservedName(eventDef, rme.newName); + } + _eventRenames.Add(eventDef, new RenameRecord + { + status = RenameStatus.NotRenamed, + signature = eventSig, + oldName = eventDef.Name, + newName = null, + renameMappingData = rme, + }); + } + } + } + } + + private void LoadXmlMappingFile(string mappingFile) + { + if (string.IsNullOrEmpty(mappingFile) || !File.Exists(mappingFile)) + { + return; + } + if (_debug) + { + Debug.Log($"skip loading debug mapping file: {Path.GetFullPath(mappingFile)}"); + return; + } + var doc = new XmlDocument(); + doc.Load(mappingFile); + var root = doc.DocumentElement; + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + LoadAssemblyMapping(element); + } + } + + private void LoadAssemblyMapping(XmlElement ele) + { + if (ele.Name != "assembly") + { + throw new System.Exception($"Invalid node name: {ele.Name}. Expected 'assembly'."); + } + + var assemblyName = ele.Attributes["name"].Value; + var rma = new RenameMappingAssembly + { + assName = assemblyName, + }; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + if (element.Name != "type") + { + throw new System.Exception($"Invalid node name: {element.Name}. Expected 'type'."); + } + LoadTypeMapping(element, rma); + } + _assemblies.Add(assemblyName, rma); + } + + private void LoadTypeMapping(XmlElement ele, RenameMappingAssembly ass) + { + var typeName = ele.Attributes["fullName"].Value; + var newTypeName = ele.Attributes["newFullName"].Value; + var rmt = new RenameMappingType + { + oldFullName = typeName, + newFullName = newTypeName, + status = (RenameStatus)System.Enum.Parse(typeof(RenameStatus), ele.Attributes["status"].Value), + }; + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement c)) + { + continue; + } + switch (node.Name) + { + case "field": LoadFieldMapping(c, rmt); break; + case "event": LoadEventMapping(c, rmt); break; + case "property": LoadPropertyMapping(c, rmt); break; + case "method": LoadMethodMapping(c, rmt); break; + default: throw new System.Exception($"Invalid node name:{node.Name}"); + } + } + ass.types.Add(typeName, rmt); + } + + private void LoadMethodMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + string oldStackTraceSignature = ele.Attributes["oldStackTraceSignature"].Value; + string newStackTraceSignature = ele.Attributes["newStackTraceSignature"].Value; + var rmm = new RenameMappingMethod + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + oldStackTraceSignature = oldStackTraceSignature, + newStackTraceSignature = newStackTraceSignature, + }; + type.methods.Add(signature, rmm); + } + + private void LoadFieldMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rmf = new RenameMappingField + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.fields.Add(signature, rmf); + } + + private void LoadPropertyMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rmp = new RenameMappingProperty + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.properties.Add(signature, rmp); + } + + private void LoadEventMapping(XmlElement ele, RenameMappingType type) + { + string signature = ele.Attributes["signature"].Value; + string newName = ele.Attributes["newName"].Value; + var rme = new RenameMappingEvent + { + signature = signature, + newName = newName, + status = RenameStatus.Renamed, + }; + type.events.Add(signature, rme); + } + + private List GetSortedValueList(Dictionary dic, Comparison comparer) + { + var list = dic.Values.ToList(); + list.Sort(comparer); + return list; + } + + public void WriteXmlMappingFile() + { + if (string.IsNullOrEmpty(_mappingFile)) + { + return; + } + var doc = new XmlDocument(); + var root = doc.CreateElement("mapping"); + doc.AppendChild(root); + + var totalAssNames = new HashSet(_modRenames.Keys.Select(m => m.Assembly.Name.ToString()).Concat(_assemblies.Keys)).ToList(); + totalAssNames.Sort((a, b) => a.CompareTo(b)); + foreach (string assName in totalAssNames) + { + ModuleDef mod = _modRenames.Keys.FirstOrDefault(m => m.Assembly.Name == assName); + var assemblyNode = doc.CreateElement("assembly"); + assemblyNode.SetAttribute("name", assName); + root.AppendChild(assemblyNode); + if (mod != null) + { + var types = mod.GetTypes().ToDictionary(t => _typeRenames.TryGetValue(t, out var rec) ? rec.oldName : t.FullName, t => t); + if (_assemblies.TryGetValue(assName, out var ass)) + { + var totalTypeNames = new HashSet(types.Keys.Concat(ass.types.Keys)).ToList(); + totalTypeNames.Sort((a, b) => a.CompareTo((b))); + foreach (string typeName in totalTypeNames) + { + if (types.TryGetValue(typeName, out TypeDef typeDef)) + { + WriteTypeMapping(assemblyNode, typeDef); + } + else if (_keepUnknownSymbolInSymbolMappingFile) + { + WriteTypeMapping(assemblyNode, typeName, ass.types[typeName]); + } + } + } + else + { + var sortedTypes = new SortedDictionary(types); + foreach (TypeDef type in sortedTypes.Values) + { + WriteTypeMapping(assemblyNode, type); + } + } + } + else + { + RenameMappingAssembly ass = _assemblies[assName]; + + var sortedTypes = GetSortedValueList(ass.types, (a, b) => a.oldFullName.CompareTo(b.oldFullName)); + foreach (var type in sortedTypes) + { + WriteTypeMapping(assemblyNode, type.oldFullName, type); + } + } + } + Directory.CreateDirectory(Path.GetDirectoryName(_mappingFile)); + doc.Save(_mappingFile); + Debug.Log($"Mapping file saved to {Path.GetFullPath(_mappingFile)}"); + } + + private void WriteTypeMapping(XmlElement assNode, TypeDef type) + { + _typeRenames.TryGetValue(type, out var record); + var typeNode = assNode.OwnerDocument.CreateElement("type"); + typeNode.SetAttribute("fullName", record?.signature ?? type.FullName); + typeNode.SetAttribute("newFullName", record != null && record.status == RenameStatus.Renamed ? record.newName : ""); + typeNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); + if (record != null && record.status == RenameStatus.Renamed) + { + Assert.IsFalse(string.IsNullOrWhiteSpace(record.newName), "New name for type cannot be null or empty when status is Renamed."); + } + + foreach (FieldDef field in type.Fields) + { + WriteFieldMapping(typeNode, field); + } + foreach (PropertyDef property in type.Properties) + { + WritePropertyMapping(typeNode, property); + } + foreach (EventDef eventDef in type.Events) + { + WriteEventMapping(typeNode, eventDef); + } + foreach (MethodDef method in type.Methods) + { + WriteMethodMapping(typeNode, method); + } + if ((record != null && record.status == RenameStatus.Renamed) || typeNode.ChildNodes.Count > 0) + { + assNode.AppendChild(typeNode); + } + } + + private void WriteTypeMapping(XmlElement assNode, string fullName, RenameMappingType type) + { + var typeNode = assNode.OwnerDocument.CreateElement("type"); + typeNode.SetAttribute("fullName", fullName); + typeNode.SetAttribute("newFullName", type.status == RenameStatus.Renamed ? type.newFullName : ""); + typeNode.SetAttribute("status", type.status.ToString()); + + foreach (var e in type.fields) + { + string signature = e.Key; + RenameMappingField field = e.Value; + WriteFieldMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.properties) + { + WritePropertyMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.events) + { + WriteEventMapping(typeNode, e.Key, e.Value); + } + foreach (var e in type.methods) + { + WriteMethodMapping(typeNode, e.Key, e.Value); + } + + assNode.AppendChild(typeNode); + } + + private void WriteFieldMapping(XmlElement typeEle, FieldDef field) + { + if (!_fieldRenames.TryGetValue(field, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var fieldNode = typeEle.OwnerDocument.CreateElement("field"); + fieldNode.SetAttribute("signature", record?.signature); + fieldNode.SetAttribute("newName", record.newName); + //fieldNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(fieldNode); + } + + private void WriteFieldMapping(XmlElement typeEle, string signature, RenameMappingField field) + { + var fieldNode = typeEle.OwnerDocument.CreateElement("field"); + fieldNode.SetAttribute("signature", signature); + fieldNode.SetAttribute("newName", field.newName); + //fieldNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(fieldNode); + } + + private void WritePropertyMapping(XmlElement typeEle, PropertyDef property) + { + if (!_propertyRenames.TryGetValue(property, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var propertyNode = typeEle.OwnerDocument.CreateElement("property"); + propertyNode.SetAttribute("signature", record.signature); + propertyNode.SetAttribute("newName", record.newName); + //propertyNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(propertyNode); + } + + private void WritePropertyMapping(XmlElement typeEle, string signature, RenameMappingProperty property) + { + var propertyNode = typeEle.OwnerDocument.CreateElement("property"); + propertyNode.SetAttribute("signature", signature); + propertyNode.SetAttribute("newName", property.newName); + //propertyNode.SetAttribute("status", record.status.ToString()); + typeEle.AppendChild(propertyNode); + } + + private void WriteEventMapping(XmlElement typeEle, EventDef eventDef) + { + if (!_eventRenames.TryGetValue(eventDef, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var eventNode = typeEle.OwnerDocument.CreateElement("event"); + eventNode.SetAttribute("signature", record.signature); + eventNode.SetAttribute("newName", record.newName); + typeEle.AppendChild(eventNode); + } + + private void WriteEventMapping(XmlElement typeEle, string signature, RenameMappingEvent eventDef) + { + var eventNode = typeEle.OwnerDocument.CreateElement("event"); + eventNode.SetAttribute("signature", signature); + eventNode.SetAttribute("newName", eventDef.newName); + typeEle.AppendChild(eventNode); + } + + private void WriteMethodMapping(XmlElement typeEle, MethodDef method) + { + if (!_methodRenames.TryGetValue(method, out var record) || record.status == RenameStatus.NotRenamed) + { + return; + } + var methodNode = typeEle.OwnerDocument.CreateElement("method"); + methodNode.SetAttribute("signature", record.signature); + methodNode.SetAttribute("newName", record.newName); + methodNode.SetAttribute("oldStackTraceSignature", record.oldStackTraceSignature); + methodNode.SetAttribute("newStackTraceSignature", MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method)); + //methodNode.SetAttribute("status", record != null ? record.status.ToString() : RenameStatus.NotRenamed.ToString()); + typeEle.AppendChild(methodNode); + } + + private void WriteMethodMapping(XmlElement typeEle, string signature, RenameMappingMethod method) + { + var methodNode = typeEle.OwnerDocument.CreateElement("method"); + methodNode.SetAttribute("signature", signature); + methodNode.SetAttribute("newName", method.newName); + methodNode.SetAttribute("oldStackTraceSignature", method.oldStackTraceSignature); + methodNode.SetAttribute("newStackTraceSignature", method.newStackTraceSignature); + typeEle.AppendChild(methodNode); + } + + public void AddRename(ModuleDef mod, string newName) + { + RenameRecord record = _modRenames[mod]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(TypeDef type, string newName) + { + RenameRecord record = _typeRenames[type]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(MethodDef method, string newName) + { + if (_methodRenames.TryGetValue(method, out RenameRecord record)) + { + record.status = RenameStatus.Renamed; + record.newName = newName; + return; + } + else + { + string methodSig = TypeSigUtil.ComputeMethodDefSignature(method); + _methodRenames.Add(method, new RenameRecord + { + status = RenameStatus.Renamed, + signature = methodSig, + oldName = method.Name, + newName = newName, + renameMappingData = null, + oldStackTraceSignature = MetaUtil.CreateMethodDefIl2CppStackTraceSignature(method), + }); + } + } + + public void InitAndAddRename(VirtualMethodGroup methodGroup, string newName) + { + RenameRecord methodRecord = methodGroup.methods.Where(m => _methodRenames.ContainsKey(m)).Select(m => _methodRenames[m]).FirstOrDefault(); + MethodDef firstMethod = methodGroup.methods[0]; + _virtualMethodGroups.Add(methodGroup, new RenameRecord + { + status = RenameStatus.Renamed, + signature = methodRecord != null ? methodRecord.signature : TypeSigUtil.ComputeMethodDefSignature(firstMethod), + oldName = methodRecord != null ? methodRecord.oldName : (string)firstMethod.Name, + newName = newName, + }); + } + + public void AddRename(FieldDef field, string newName) + { + RenameRecord record = _fieldRenames[field]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(PropertyDef property, string newName) + { + RenameRecord record = _propertyRenames[property]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public void AddRename(EventDef eventDef, string newName) + { + RenameRecord record = _eventRenames[eventDef]; + record.status = RenameStatus.Renamed; + record.newName = newName; + } + + public bool TryGetExistRenameMapping(TypeDef type, out string newNamespace, out string newName) + { + if (_typeRenames.TryGetValue(type, out var record) && record.renameMappingData != null) + { + var rmt = (RenameMappingType)record.renameMappingData; + if (rmt.status == RenameStatus.Renamed) + { + Assert.IsFalse(string.IsNullOrWhiteSpace(rmt.newFullName)); + (newNamespace, newName) = MetaUtil.SplitNamespaceAndName(rmt.newFullName); + return true; + } + } + newNamespace = null; + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(MethodDef method, out string newName) + { + if (_methodRenames.TryGetValue(method, out var record) && record.renameMappingData != null) + { + RenameMappingMethod rmm = (RenameMappingMethod)record.renameMappingData; + if (rmm.status == RenameStatus.Renamed) + { + newName = ((RenameMappingMethod)record.renameMappingData).newName; + return true; + } + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(FieldDef field, out string newName) + { + if (_fieldRenames.TryGetValue(field, out var record) && record.renameMappingData != null) + { + RenameMappingField rmm = (RenameMappingField)record.renameMappingData; + if (rmm.status == RenameStatus.Renamed) + { + newName = ((RenameMappingField)record.renameMappingData).newName; + return true; + } + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(PropertyDef property, out string newName) + { + if (_propertyRenames.TryGetValue(property, out var record) && record.renameMappingData != null) + { + RenameMappingProperty rmm = (RenameMappingProperty)record.renameMappingData; + if (rmm.status == RenameStatus.Renamed) + { + newName = ((RenameMappingProperty)record.renameMappingData).newName; + return true; + } + } + newName = null; + return false; + } + + public bool TryGetExistRenameMapping(EventDef eventDef, out string newName) + { + if (_eventRenames.TryGetValue(eventDef, out var record) && record.renameMappingData != null) + { + RenameMappingEvent rmm = (RenameMappingEvent)record.renameMappingData; + if (rmm.status == RenameStatus.Renamed) + { + newName = ((RenameMappingEvent)record.renameMappingData).newName; + return true; + } + } + newName = null; + return false; + } + + public bool TryGetRename(VirtualMethodGroup group, out string newName) + { + if (_virtualMethodGroups.TryGetValue(group, out var record)) + { + newName = record.newName; + return true; + } + newName = null; + return false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta new file mode 100644 index 00000000..2a91341f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/RenameRecordMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3ffbd28624d87c4382f62eb4fc19c70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs new file mode 100644 index 00000000..eb7970aa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs @@ -0,0 +1,31 @@ +using Obfuz.Settings; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public class SymbolObfusPass : ObfuscationPassBase + { + private SymbolRename _symbolRename; + + public override ObfuscationPassType Type => ObfuscationPassType.SymbolObfus; + + public SymbolObfusPass(SymbolObfuscationSettingsFacade settings) + { + _symbolRename = new SymbolRename(settings); + } + + public override void Start() + { + _symbolRename.Init(); + } + + public override void Stop() + { + _symbolRename.Save(); + } + + public override void Process() + { + _symbolRename.Process(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta new file mode 100644 index 00000000..a86c04b1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolObfusPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4c9a37b51ade6b46a299be7ad2155d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs new file mode 100644 index 00000000..1ceddfa7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs @@ -0,0 +1,864 @@ +using dnlib.DotNet; +using Obfuz.ObfusPasses.SymbolObfus.NameMakers; +using Obfuz.ObfusPasses.SymbolObfus.Policies; +using Obfuz.Settings; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + public class SymbolRename + { + private readonly bool _useConsistentNamespaceObfuscation; + private readonly bool _detectReflectionCompatibility; + private readonly List _obfuscationRuleFiles; + private readonly string _mappingXmlPath; + + private AssemblyCache _assemblyCache; + + private List _toObfuscatedModules; + private List _obfuscatedAndNotObfuscatedModules; + private HashSet _toObfuscatedModuleSet; + private HashSet _nonObfuscatedButReferencingObfuscatedModuleSet; + private IObfuscationPolicy _renamePolicy; + private INameMaker _nameMaker; + private readonly Dictionary> _customAttributeArgumentsWithTypeByMods = new Dictionary>(); + private readonly RenameRecordMap _renameRecordMap; + private readonly VirtualMethodGroupCalculator _virtualMethodGroupCalculator; + private readonly List _customPolicies = new List(); + + class CustomAttributeInfo + { + public CustomAttributeCollection customAttributes; + public int index; + public List arguments; + public List namedArguments; + } + + public SymbolRename(SymbolObfuscationSettingsFacade settings) + { + _useConsistentNamespaceObfuscation = settings.useConsistentNamespaceObfuscation; + _detectReflectionCompatibility = settings.detectReflectionCompatibility; + _mappingXmlPath = settings.symbolMappingFile; + _obfuscationRuleFiles = settings.ruleFiles.ToList(); + _renameRecordMap = new RenameRecordMap(settings.symbolMappingFile, settings.debug, settings.keepUnknownSymbolInSymbolMappingFile); + _virtualMethodGroupCalculator = new VirtualMethodGroupCalculator(); + _nameMaker = settings.debug ? NameMakerFactory.CreateDebugNameMaker() : NameMakerFactory.CreateNameMakerBaseASCIICharSet(settings.obfuscatedNamePrefix); + + foreach (var customPolicyType in settings.customRenamePolicyTypes) + { + if (Activator.CreateInstance(customPolicyType, new object[] { this }) is IObfuscationPolicy customPolicy) + { + _customPolicies.Add(customPolicy); + } + else + { + Debug.LogWarning($"Custom rename policy type {customPolicyType} is not a valid IObfuscationPolicy"); + } + } + } + + public void Init() + { + var ctx = ObfuscationPassContext.Current; + _assemblyCache = ctx.assemblyCache; + _toObfuscatedModules = ctx.modulesToObfuscate; + _obfuscatedAndNotObfuscatedModules = ctx.allObfuscationRelativeModules; + _toObfuscatedModuleSet = new HashSet(ctx.modulesToObfuscate); + _nonObfuscatedButReferencingObfuscatedModuleSet = new HashSet(ctx.allObfuscationRelativeModules.Where(m => !_toObfuscatedModuleSet.Contains(m))); + + var obfuscateRuleConfig = new ConfigurableRenamePolicy(ctx.coreSettings.assembliesToObfuscate, ctx.modulesToObfuscate, _obfuscationRuleFiles); + var totalRenamePolicies = new List + { + new SupportPassPolicy(ctx.passPolicy), + new SystemRenamePolicy(ctx.obfuzIgnoreScopeComputeCache), + new UnityRenamePolicy(), + obfuscateRuleConfig, + }; + totalRenamePolicies.AddRange(_customPolicies); + + _renamePolicy = new CacheRenamePolicy(new CombineRenamePolicy(totalRenamePolicies.ToArray())); + BuildCustomAttributeArguments(); + } + + private void CollectCArgumentWithTypeOf(IHasCustomAttribute meta, List customAttributes) + { + int index = 0; + foreach (CustomAttribute ca in meta.CustomAttributes) + { + List arguments = null; + if (ca.ConstructorArguments.Any(a => MetaUtil.MayRenameCustomDataType(a.Type.ElementType))) + { + arguments = ca.ConstructorArguments.ToList(); + } + List namedArguments = ca.NamedArguments.Count > 0 ? ca.NamedArguments.ToList() : null; + if (arguments != null || namedArguments != null) + { + customAttributes.Add(new CustomAttributeInfo + { + customAttributes = meta.CustomAttributes, + index = index, + arguments = arguments, + namedArguments = namedArguments + }); + } + ++index; + } + } + + private void BuildCustomAttributeArguments() + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + var customAttributes = new List(); + CollectCArgumentWithTypeOf(mod, customAttributes); + foreach (TypeDef type in mod.GetTypes()) + { + CollectCArgumentWithTypeOf(type, customAttributes); + foreach (FieldDef field in type.Fields) + { + CollectCArgumentWithTypeOf(field, customAttributes); + } + foreach (MethodDef method in type.Methods) + { + CollectCArgumentWithTypeOf(method, customAttributes); + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + CollectCArgumentWithTypeOf(param.ParamDef, customAttributes); + } + } + } + foreach (PropertyDef property in type.Properties) + { + CollectCArgumentWithTypeOf(property, customAttributes); + } + foreach (EventDef eventDef in type.Events) + { + CollectCArgumentWithTypeOf(eventDef, customAttributes); + } + } + + _customAttributeArgumentsWithTypeByMods.Add(mod, customAttributes); + } + } + + private void PrecomputeNeedRename() + { + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + _renamePolicy.NeedRename(type); + foreach (var field in type.Fields) + { + _renamePolicy.NeedRename(field); + } + foreach (var method in type.Methods) + { + _renamePolicy.NeedRename(method); + } + foreach (var property in type.Properties) + { + _renamePolicy.NeedRename(property); + } + foreach (var eventDef in type.Events) + { + _renamePolicy.NeedRename(eventDef); + } + } + } + } + + public void Process() + { + _renameRecordMap.Init(_toObfuscatedModules, _nameMaker); + PrecomputeNeedRename(); + if (_detectReflectionCompatibility) + { + var reflectionCompatibilityDetector = new ReflectionCompatibilityDetector(_toObfuscatedModules, _obfuscatedAndNotObfuscatedModules, _renamePolicy); + reflectionCompatibilityDetector.Analyze(); + } + RenameTypes(); + RenameFields(); + RenameMethods(); + RenameProperties(); + RenameEvents(); + } + + class RefTypeDefMetas + { + public readonly List typeRefs = new List(); + + public readonly List customAttributes = new List(); + } + + private void BuildRefTypeDefMetasMap(Dictionary refTypeDefMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (TypeRef typeRef in mod.GetTypeRefs()) + { + if (typeRef.DefinitionAssembly.IsCorLib()) + { + continue; + } + TypeDef typeDef = typeRef.ResolveThrow(); + if (!refTypeDefMetasMap.TryGetValue(typeDef, out var typeDefMetas)) + { + typeDefMetas = new RefTypeDefMetas(); + refTypeDefMetasMap.Add(typeDef, typeDefMetas); + } + typeDefMetas.typeRefs.Add(typeRef); + } + } + + foreach (CustomAttributeInfo cai in _customAttributeArgumentsWithTypeByMods.Values.SelectMany(cas => cas)) + { + CustomAttribute ca = cai.customAttributes[cai.index]; + TypeDef typeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(ca.Constructor.DeclaringType); + if (!refTypeDefMetasMap.TryGetValue(typeDef, out var typeDefMetas)) + { + typeDefMetas = new RefTypeDefMetas(); + refTypeDefMetasMap.Add(typeDef, typeDefMetas); + } + typeDefMetas.customAttributes.Add(ca); + } + } + + private void RetargetTypeRefInCustomAttributes() + { + foreach (CustomAttributeInfo cai in _customAttributeArgumentsWithTypeByMods.Values.SelectMany(cas => cas)) + { + CustomAttribute ca = cai.customAttributes[cai.index]; + bool anyChange = false; + if (cai.arguments != null) + { + for (int i = 0; i < cai.arguments.Count; i++) + { + CAArgument oldArg = cai.arguments[i]; + if (MetaUtil.TryRetargetTypeRefInArgument(oldArg, out CAArgument newArg)) + { + anyChange = true; + cai.arguments[i] = newArg; + } + } + } + if (cai.namedArguments != null) + { + for (int i = 0; i < cai.namedArguments.Count; i++) + { + if (MetaUtil.TryRetargetTypeRefInNamedArgument(cai.namedArguments[i])) + { + anyChange = true; + } + } + } + if (anyChange) + { + cai.customAttributes[cai.index] = new CustomAttribute(ca.Constructor, + cai.arguments != null ? cai.arguments : ca.ConstructorArguments, + cai.namedArguments != null ? cai.namedArguments : ca.NamedArguments); + } + } + } + + private readonly Dictionary _refTypeRefMetasMap = new Dictionary(); + + private void RenameTypes() + { + //Debug.Log("RenameTypes begin"); + + RetargetTypeRefInCustomAttributes(); + + BuildRefTypeDefMetasMap(_refTypeRefMetasMap); + _assemblyCache.EnableTypeDefCache = false; + + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + if (_renamePolicy.NeedRename(type)) + { + Rename(type, _refTypeRefMetasMap.GetValueOrDefault(type)); + } + } + } + + // clean cache + _assemblyCache.EnableTypeDefCache = true; + //Debug.Log("Rename Types end"); + } + + + class RefFieldMetas + { + public readonly List fieldRefs = new List(); + public readonly List customAttributes = new List(); + } + + + private void BuildHierarchyFields(TypeDef type, List fields) + { + while (type != null) + { + fields.AddRange(type.Fields); + type = MetaUtil.GetBaseTypeDef(type); + } + } + + private IEnumerable WalkAllMethodInstructionOperand(ModuleDef mod) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (!method.HasBody) + { + continue; + } + foreach (var instr in method.Body.Instructions) + { + if (instr.Operand is T memberRef) + { + yield return memberRef; + } + } + } + } + } + + private void BuildRefFieldMetasMap(Dictionary refFieldMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (MemberRef memberRef in WalkAllMethodInstructionOperand(mod)) + { + IMemberRefParent parent = memberRef.Class; + TypeDef parentTypeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(parent); + if (parentTypeDef == null) + { + continue; + } + foreach (FieldDef field in parentTypeDef.Fields) + { + if (field.Name == memberRef.Name && TypeEqualityComparer.Instance.Equals(field.FieldSig.Type, memberRef.FieldSig.Type)) + { + if (!refFieldMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefFieldMetas(); + refFieldMetasMap.Add(field, fieldMetas); + } + fieldMetas.fieldRefs.Add(memberRef); + break; + } + } + } + } + foreach (var e in _refTypeRefMetasMap) + { + TypeDef typeDef = e.Key; + var hierarchyFields = new List(); + BuildHierarchyFields(typeDef, hierarchyFields); + RefTypeDefMetas typeDefMetas = e.Value; + foreach (CustomAttribute ca in typeDefMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.IsProperty) + { + continue; + } + foreach (FieldDef field in hierarchyFields) + { + // FIXME. field of Generic Base Type may not be same + if (field.Name == arg.Name && TypeEqualityComparer.Instance.Equals(field.FieldType, arg.Type)) + { + if (!refFieldMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefFieldMetas(); + refFieldMetasMap.Add(field, fieldMetas); + } + fieldMetas.customAttributes.Add(ca); + break; + } + } + } + } + } + } + + private void RenameFields() + { + //Debug.Log("Rename fields begin"); + var refFieldMetasMap = new Dictionary(); + BuildRefFieldMetasMap(refFieldMetasMap); + + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (FieldDef field in type.Fields) + { + if (_renamePolicy.NeedRename(field)) + { + Rename(field, refFieldMetasMap.GetValueOrDefault(field)); + } + } + } + } + //Debug.Log("Rename fields end"); + } + + class RefMethodMetas + { + public readonly List memberRefs = new List(); + } + + private void RenameMethodRef(MemberRef memberRef, Dictionary refMethodMetasMap) + { + if (!memberRef.IsMethodRef) + { + return; + } + + IMemberRefParent parent = memberRef.Class; + TypeDef parentTypeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(parent); + if (parentTypeDef == null) + { + return; + } + foreach (MethodDef methodDef in parentTypeDef.Methods) + { + if (methodDef.Name == memberRef.Name && new SigComparer(default).Equals(methodDef.MethodSig, memberRef.MethodSig)) + { + if (!refMethodMetasMap.TryGetValue(methodDef, out var refMethodMetas)) + { + refMethodMetas = new RefMethodMetas(); + refMethodMetasMap.Add(methodDef, refMethodMetas); + } + refMethodMetas.memberRefs.Add(memberRef); + break; + } + } + } + + + private void RenameMethodRefOrMethodSpec(IMethod method, Dictionary refMethodMetasMap) + { + if (method is MemberRef memberRef) + { + RenameMethodRef(memberRef, refMethodMetasMap); + } + else if (method is MethodSpec methodSpec) + { + if (methodSpec.Method is MemberRef memberRef2) + { + RenameMethodRef(memberRef2, refMethodMetasMap); + } + } + } + + private void BuildRefMethodMetasMap(Dictionary refMethodMetasMap) + { + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (IMethod method in WalkAllMethodInstructionOperand(mod)) + { + RenameMethodRefOrMethodSpec(method, refMethodMetasMap); + } + + foreach (var type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.HasOverrides) + { + foreach (MethodOverride methodOverride in method.Overrides) + { + RenameMethodRefOrMethodSpec(methodOverride.MethodDeclaration, refMethodMetasMap); + RenameMethodRefOrMethodSpec(methodOverride.MethodBody, refMethodMetasMap); + } + } + } + } + foreach (var e in _refTypeRefMetasMap) + { + TypeDef typeDef = e.Key; + var hierarchyFields = new List(); + BuildHierarchyFields(typeDef, hierarchyFields); + RefTypeDefMetas typeDefMetas = e.Value; + foreach (CustomAttribute ca in typeDefMetas.customAttributes) + { + if (ca.Constructor is IMethod method) + { + RenameMethodRefOrMethodSpec(method, refMethodMetasMap); + } + } + } + } + } + + private void RenameMethods() + { + //Debug.Log("Rename methods begin"); + //Debug.Log("Rename not virtual methods begin"); + var virtualMethods = new List(); + var refMethodMetasMap = new Dictionary(); + BuildRefMethodMetasMap(refMethodMetasMap); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (MethodDef method in type.Methods) + { + if (method.IsVirtual) + { + continue; + } + if (_renamePolicy.NeedRename(method)) + { + Rename(method, refMethodMetasMap.GetValueOrDefault(method)); + } + } + } + } + + foreach (ModuleDef mod in _obfuscatedAndNotObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + _virtualMethodGroupCalculator.CalculateType(type); + foreach (MethodDef method in type.Methods) + { + if (method.IsVirtual) + { + virtualMethods.Add(method); + } + } + } + } + + //Debug.Log("Rename not virtual methods end"); + + + //Debug.Log("Rename virtual methods begin"); + var visitedVirtualMethods = new HashSet(); + var groupNeedRenames = new Dictionary(); + foreach (var method in virtualMethods) + { + if (!visitedVirtualMethods.Add(method)) + { + continue; + } + VirtualMethodGroup group = _virtualMethodGroupCalculator.GetMethodGroup(method); + if (!groupNeedRenames.TryGetValue(group, out var needRename)) + { + var rootBeInheritedTypes = group.GetRootBeInheritedTypes(); + // - if the group contains no obfuscated methods + // - if the group contains method defined in non-obfuscated module but referencing obfuscated module and virtual method in obfuscated type overrides virtual method from non-obfuscated type + if (!group.methods.Any(m => _toObfuscatedModuleSet.Contains(m.DeclaringType.Module)) || group.methods.Any(m => _nonObfuscatedButReferencingObfuscatedModuleSet.Contains(m.Module) && rootBeInheritedTypes.Contains(m.DeclaringType))) + { + needRename = false; + } + else + { + needRename = group.methods.All(m => _obfuscatedAndNotObfuscatedModules.Contains(m.Module) && _renamePolicy.NeedRename(m)); + } + groupNeedRenames.Add(group, needRename); + if (needRename) + { + bool conflict = false; + string newVirtualMethodName = null; + foreach (MethodDef m in group.methods) + { + if (_renameRecordMap.TryGetExistRenameMapping(m, out var existVirtualMethodName)) + { + if (newVirtualMethodName == null) + { + newVirtualMethodName = existVirtualMethodName; + } + else if (newVirtualMethodName != existVirtualMethodName) + { + Debug.LogWarning($"Virtual method rename conflict. {m} => {existVirtualMethodName} != {newVirtualMethodName}"); + conflict = true; + break; + } + } + } + if (newVirtualMethodName == null || conflict /*|| _nameMaker.IsNamePreserved(group, newVirtualMethodName)*/) + { + newVirtualMethodName = _nameMaker.GetNewName(group, method.Name); + } + _renameRecordMap.InitAndAddRename(group, newVirtualMethodName); + } + } + if (!needRename) + { + continue; + } + if (_renameRecordMap.TryGetRename(group, out var newName)) + { + Rename(method, refMethodMetasMap.GetValueOrDefault(method), newName); + } + else + { + throw new Exception($"group:{group} method:{method} not found in rename record map"); + } + } + //Debug.Log("Rename virtual methods end"); + //Debug.Log("Rename methods end"); + } + + class RefPropertyMetas + { + public List customAttributes = new List(); + } + + private void BuildHierarchyProperties(TypeDef type, List properties) + { + while (type != null) + { + properties.AddRange(type.Properties); + type = MetaUtil.GetBaseTypeDef(type); + } + } + + private void BuildRefPropertyMetasMap(Dictionary refPropertyMetasMap) + { + foreach (var e in _refTypeRefMetasMap) + { + TypeDef typeDef = e.Key; + var hierarchyProperties = new List(); + BuildHierarchyProperties(typeDef, hierarchyProperties); + RefTypeDefMetas typeDefMetas = e.Value; + foreach (CustomAttribute ca in typeDefMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.IsField) + { + continue; + } + foreach (PropertyDef field in hierarchyProperties) + { + // FIXME. field of Generic Base Type may not be same + if (field.Name == arg.Name && TypeEqualityComparer.Instance.Equals(arg.Type, field.PropertySig.RetType)) + { + if (!refPropertyMetasMap.TryGetValue(field, out var fieldMetas)) + { + fieldMetas = new RefPropertyMetas(); + refPropertyMetasMap.Add(field, fieldMetas); + } + fieldMetas.customAttributes.Add(ca); + break; + } + } + } + } + } + } + + private void RenameProperties() + { + //Debug.Log("Rename properties begin"); + var refPropertyMetasMap = new Dictionary(); + BuildRefPropertyMetasMap(refPropertyMetasMap); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (PropertyDef property in type.Properties) + { + if (_renamePolicy.NeedRename(property)) + { + Rename(property, refPropertyMetasMap.GetValueOrDefault(property)); + } + } + } + } + //Debug.Log("Rename properties end"); + } + + private void RenameEvents() + { + //Debug.Log("Rename events begin"); + foreach (ModuleDef mod in _toObfuscatedModules) + { + foreach (TypeDef type in mod.GetTypes()) + { + foreach (EventDef eventDef in type.Events) + { + if (_renamePolicy.NeedRename(eventDef)) + { + Rename(eventDef); + } + } + } + } + //Debug.Log("Rename events begin"); + } + + private void Rename(TypeDef type, RefTypeDefMetas refTypeDefMeta) + { + string moduleName = MetaUtil.GetModuleNameWithoutExt(type.Module.Name); + string oldFullName = type.FullName; + string oldNamespace = type.Namespace; + + string oldName = type.Name; + + string newNamespace; + string newName; + if (_renameRecordMap.TryGetExistRenameMapping(type, out var nns, out var nn)) + { + newNamespace = nns; + newName = nn; + } + else + { + newNamespace = _nameMaker.GetNewNamespace(type, oldNamespace, _useConsistentNamespaceObfuscation); + newName = _nameMaker.GetNewName(type, oldName); + } + + if (refTypeDefMeta != null) + { + foreach (TypeRef typeRef in refTypeDefMeta.typeRefs) + { + Assert.AreEqual(typeRef.FullName, oldFullName); + Assert.IsTrue(typeRef.DefinitionAssembly.Name == moduleName); + if (!string.IsNullOrEmpty(oldNamespace)) + { + typeRef.Namespace = newNamespace; + } + typeRef.Name = newName; + //Debug.Log($"rename assembly:{typeRef.Module.Name} reference {oldFullName} => {typeRef.FullName}"); + } + } + type.Name = newName; + type.Namespace = newNamespace; + string newFullName = type.FullName; + _renameRecordMap.AddRename(type, newFullName); + //Debug.Log($"rename typedef. assembly:{type.Module.Name} oldName:{oldFullName} => newName:{newFullName}"); + } + + private void Rename(FieldDef field, RefFieldMetas fieldMetas) + { + string oldName = field.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(field, out var nn) ? nn : _nameMaker.GetNewName(field, oldName); + if (fieldMetas != null) + { + foreach (var memberRef in fieldMetas.fieldRefs) + { + memberRef.Name = newName; + //Debug.Log($"rename assembly:{memberRef.Module.Name} reference {field.FullName} => {memberRef.FullName}"); + } + foreach (var ca in fieldMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.Name == oldName) + { + arg.Name = newName; + } + } + } + } + //Debug.Log($"rename field. {field} => {newName}"); + _renameRecordMap.AddRename(field, newName); + field.Name = newName; + + } + + private void Rename(MethodDef method, RefMethodMetas refMethodMetas) + { + string oldName = method.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(method, out var nn) ? nn : _nameMaker.GetNewName(method, oldName); + Rename(method, refMethodMetas, newName); + } + + private void Rename(MethodDef method, RefMethodMetas refMethodMetas, string newName) + { + ModuleDefMD mod = (ModuleDefMD)method.DeclaringType.Module; + RenameMethodParams(method); + RenameMethodBody(method); + if (refMethodMetas != null) + { + foreach (MemberRef memberRef in refMethodMetas.memberRefs) + { + string oldMethodFullName = memberRef.ToString(); + memberRef.Name = newName; + //Debug.Log($"rename assembly:{memberRef.Module.Name} method:{oldMethodFullName} => {memberRef}"); + } + } + _renameRecordMap.AddRename(method, newName); + method.Name = newName; + } + + private void RenameMethodBody(MethodDef method) + { + if (method.Body == null) + { + return; + } + } + + private void RenameMethodParams(MethodDef method) + { + foreach (Parameter param in method.Parameters) + { + if (param.ParamDef != null) + { + Rename(param.ParamDef); + } + } + } + + private void Rename(ParamDef param) + { + string newName = _nameMaker.GetNewName(param, param.Name); + param.Name = newName; + } + + private void Rename(EventDef eventDef) + { + string oldName = eventDef.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(eventDef, out var nn) ? nn : _nameMaker.GetNewName(eventDef, eventDef.Name); + _renameRecordMap.AddRename(eventDef, newName); + eventDef.Name = newName; + } + + private void Rename(PropertyDef property, RefPropertyMetas refPropertyMetas) + { + string oldName = property.Name; + string newName = _renameRecordMap.TryGetExistRenameMapping(property, out var nn) ? nn : _nameMaker.GetNewName(property, oldName); + + if (refPropertyMetas != null) + { + foreach (var ca in refPropertyMetas.customAttributes) + { + foreach (var arg in ca.NamedArguments) + { + if (arg.Name == oldName) + { + arg.Name = newName; + } + } + } + } + _renameRecordMap.AddRename(property, newName); + property.Name = newName; + } + + public void Save() + { + Directory.CreateDirectory(Path.GetDirectoryName(_mappingXmlPath)); + _renameRecordMap.WriteXmlMappingFile(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta new file mode 100644 index 00000000..739a842b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/SymbolRename.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec29be12f08e90741a59970f029c2eec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs new file mode 100644 index 00000000..ac6b4546 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs @@ -0,0 +1,299 @@ +using dnlib.DotNet; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.SymbolObfus +{ + + public class VirtualMethodGroup + { + public List methods; + + private HashSet _nameScopes; + + private HashSet _rootNameScope; + + public HashSet GetNameConflictTypeScopes() + { + if (_nameScopes != null) + { + return _nameScopes; + } + + _nameScopes = new HashSet(); + foreach (var method in methods) + { + TypeDef cur = method.DeclaringType; + while (cur != null) + { + _nameScopes.Add(cur); + cur = MetaUtil.GetBaseTypeDef(cur); + } + } + return _nameScopes; + } + + public HashSet GetRootBeInheritedTypes() + { + if (_rootNameScope != null) + { + return _rootNameScope; + } + _rootNameScope = new HashSet(); + var nameScopes = GetNameConflictTypeScopes(); + foreach (var type in nameScopes) + { + TypeDef parentType = MetaUtil.GetBaseTypeDef(type); + if (parentType == null || !nameScopes.Contains(parentType)) + { + _rootNameScope.Add(type); + } + } + return _rootNameScope; + } + + public IEnumerable GetNameDeclaringTypeScopes() + { + foreach (var method in methods) + { + yield return method.DeclaringType; + } + } + } + + public class VirtualMethodGroupCalculator + { + + private class TypeFlatMethods + { + public HashSet flatMethods = new HashSet(); + + + private bool IsFinalTypeSig(TypeSig type) + { + switch (type.ElementType) + { + case ElementType.Void: + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.I2: + case ElementType.I4: + case ElementType.I8: + case ElementType.U1: + case ElementType.U2: + case ElementType.U4: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.String: + case ElementType.Object: + case ElementType.Class: + case ElementType.ValueType: + return true; + default: return false; + } + } + + private bool IsVarType(TypeSig t) + { + return t.ElementType == ElementType.MVar || t.ElementType == ElementType.Var; + } + + private bool IsClassOrValueType(TypeSig t) + { + return t.ElementType == ElementType.Class || t.ElementType == ElementType.ValueType; + } + + private bool IsLooseTypeSigMatch(TypeSig t1, TypeSig t2) + { + t1 = t1.RemovePinnedAndModifiers(); + t2 = t2.RemovePinnedAndModifiers(); + + if (t1.ElementType != t2.ElementType) + { + return IsVarType(t1) || IsVarType(t2); + } + + switch (t1.ElementType) + { + case ElementType.Void: + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.I2: + case ElementType.I4: + case ElementType.I8: + case ElementType.U1: + case ElementType.U2: + case ElementType.U4: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.I: + case ElementType.U: + case ElementType.R: + case ElementType.String: + case ElementType.Object: + case ElementType.TypedByRef: + return true; + case ElementType.Class: + case ElementType.ValueType: + { + return t1.AssemblyQualifiedName == t2.AssemblyQualifiedName; + } + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.SZArray: + { + break; + } + case ElementType.Array: + { + var a1 = (ArraySig)t1; + var a2 = (ArraySig)t2; + if (a1.Rank != a2.Rank) + { + return false; + } + break; + } + case ElementType.Var: + case ElementType.MVar: + { + //var v1 = (GenericSig)t1; + //var v2 = (GenericSig)t2; + //return v1.Number == v2.Number; + return true; + } + default: return true; + } + if (t1.Next != null && t2.Next != null) + { + return IsLooseTypeSigMatch(t1.Next, t2.Next); + } + return true; + } + + private bool IsLooseMatch(MethodDef method1, MethodDef method2) + { + if (method1.Name != method2.Name) + { + return false; + } + if (method1.GetParamCount() != method2.GetParamCount()) + { + return false; + } + if (!IsLooseTypeSigMatch(method1.ReturnType, method2.ReturnType)) + { + return false; + } + for (int i = 0, n = method1.GetParamCount(); i < n; i++) + { + if (!IsLooseTypeSigMatch(method1.GetParam(i), method2.GetParam(i))) + { + return false; + } + } + + return true; + } + + public bool TryFindMatchVirtualMethod(MethodDef method, out MethodDef matchMethodDef) + { + foreach (var parentOrInterfaceMethod in flatMethods) + { + if (IsLooseMatch(method, parentOrInterfaceMethod)) + { + matchMethodDef = parentOrInterfaceMethod; + return true; + } + } + matchMethodDef = null; + return false; + } + } + + + private readonly Dictionary _methodGroups = new Dictionary(); + private readonly Dictionary _visitedTypes = new Dictionary(); + + + + public VirtualMethodGroup GetMethodGroup(MethodDef methodDef) + { + if (_methodGroups.TryGetValue(methodDef, out var group)) + { + return group; + } + return null; + } + + public void CalculateType(TypeDef typeDef) + { + if (_visitedTypes.ContainsKey(typeDef)) + { + return; + } + + var typeMethods = new TypeFlatMethods(); + + var interfaceMethods = new List(); + if (typeDef.BaseType != null) + { + TypeDef baseTypeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(typeDef.BaseType); + CalculateType(baseTypeDef); + typeMethods.flatMethods.AddRange(_visitedTypes[baseTypeDef].flatMethods); + foreach (var intfType in typeDef.Interfaces) + { + TypeDef intfTypeDef = MetaUtil.GetTypeDefOrGenericTypeBaseThrowException(intfType.Interface); + CalculateType(intfTypeDef); + //typeMethods.flatMethods.AddRange(_visitedTypes[intfTypeDef].flatMethods); + interfaceMethods.AddRange(_visitedTypes[intfTypeDef].flatMethods); + } + } + foreach (MethodDef method in interfaceMethods) + { + if (typeMethods.TryFindMatchVirtualMethod(method, out var matchMethodDef)) + { + // merge group + var group = _methodGroups[matchMethodDef]; + var matchGroup = _methodGroups[method]; + if (group != matchGroup) + { + foreach (var m in matchGroup.methods) + { + group.methods.Add(m); + _methodGroups[m] = group; + } + } + } + typeMethods.flatMethods.Add(method); + } + + foreach (MethodDef method in typeDef.Methods) + { + if (!method.IsVirtual) + { + continue; + } + if (typeMethods.TryFindMatchVirtualMethod(method, out var matchMethodDef)) + { + var group = _methodGroups[matchMethodDef]; + group.methods.Add(method); + _methodGroups.Add(method, group); + } + else + { + _methodGroups.Add(method, new VirtualMethodGroup() { methods = new List { method } }); + } + if (method.IsNewSlot) + { + typeMethods.flatMethods.Add(method); + } + } + _visitedTypes.Add(typeDef, typeMethods); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta new file mode 100644 index 00000000..097106aa --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfusPasses/SymbolObfus/VirtualMethodGroupCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 649aeb6306500a04f8b7a3e01f5aaf0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs new file mode 100644 index 00000000..4fe07f3e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs @@ -0,0 +1,110 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using Obfuz.Utils; +using System.Linq; +using UnityEngine; + +namespace Obfuz +{ + public class ObfuscationMethodWhitelist + { + private readonly ObfuzIgnoreScopeComputeCache _obfuzComputeCache; + private readonly BurstCompileComputeCache _burstCompileComputeCache; + + public ObfuscationMethodWhitelist(ObfuzIgnoreScopeComputeCache obfuzComputeCache, BurstCompileComputeCache burstCompileComputeCache) + { + _obfuzComputeCache = obfuzComputeCache; + _burstCompileComputeCache = burstCompileComputeCache; + } + + public bool IsInWhiteList(ModuleDef module) + { + string modName = module.Assembly.Name; + if (modName == ConstValues.ObfuzRuntimeAssemblyName) + { + return true; + } + //if (MetaUtil.HasObfuzIgnoreScope(module)) + //{ + // return true; + //} + return false; + } + + private bool DoesMethodContainsRuntimeInitializeOnLoadMethodAttributeAndLoadTypeGreaterEqualAfterAssembliesLoaded(MethodDef method) + { + CustomAttribute ca = method.CustomAttributes.Find(ConstValues.RuntimeInitializedOnLoadMethodAttributeFullName); + if (ca != null && ca.ConstructorArguments.Count > 0) + { + RuntimeInitializeLoadType loadType = (RuntimeInitializeLoadType)ca.ConstructorArguments[0].Value; + if (loadType >= RuntimeInitializeLoadType.AfterAssembliesLoaded) + { + return true; + } + } + return false; + } + + public bool IsInWhiteList(MethodDef method) + { + TypeDef typeDef = method.DeclaringType; + //if (IsInWhiteList(typeDef)) + //{ + // return true; + //} + if (method.Name.StartsWith(ConstValues.ObfuzInternalSymbolNamePrefix)) + { + return true; + } + if (_obfuzComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(method, typeDef, ObfuzScope.MethodBody)) + { + return true; + } + CustomAttribute ca = method.CustomAttributes.Find(ConstValues.RuntimeInitializedOnLoadMethodAttributeFullName); + if (DoesMethodContainsRuntimeInitializeOnLoadMethodAttributeAndLoadTypeGreaterEqualAfterAssembliesLoaded(method)) + { + return true; + } + if (method.CustomAttributes.Find(ConstValues.BurstCompileFullName) != null || _burstCompileComputeCache.IsBurstCompileMethodOrReferencedByBurstCompileMethod(method)) + { + return true; + } + + // don't obfuscate cctor when it has RuntimeInitializeOnLoadMethodAttribute with load type AfterAssembliesLoaded + if (method.IsStatic && method.Name == ".cctor" && typeDef.Methods.Any(m => DoesMethodContainsRuntimeInitializeOnLoadMethodAttributeAndLoadTypeGreaterEqualAfterAssembliesLoaded(m))) + { + return true; + } + return false; + } + + public bool IsInWhiteList(TypeDef type) + { + if (type.Name.StartsWith(ConstValues.ObfuzInternalSymbolNamePrefix)) + { + return true; + } + if (IsInWhiteList(type.Module)) + { + return true; + } + if (type.CustomAttributes.Find(ConstValues.BurstCompileFullName) != null) + { + return true; + } + if (_obfuzComputeCache.HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(type, type.DeclaringType, ObfuzScope.TypeName)) + { + return true; + } + //if (type.DeclaringType != null && IsInWhiteList(type.DeclaringType)) + //{ + // return true; + //} + if (type.FullName == ConstValues.GeneratedEncryptionVirtualMachineFullName) + { + return true; + } + return false; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta new file mode 100644 index 00000000..0ef3bf32 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationMethodWhitelist.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8824d47fdc31cef41a899294491c8844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs new file mode 100644 index 00000000..7f2f60f1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs @@ -0,0 +1,70 @@ +using dnlib.DotNet; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.Utils; +using System.Collections.Generic; + +namespace Obfuz +{ + public delegate IRandom RandomCreator(int seed); + + public class EncryptionScopeInfo + { + public readonly IEncryptor encryptor; + public readonly RandomCreator localRandomCreator; + + public EncryptionScopeInfo(IEncryptor encryptor, RandomCreator localRandomCreator) + { + this.encryptor = encryptor; + this.localRandomCreator = localRandomCreator; + } + } + + public class EncryptionScopeProvider + { + private readonly EncryptionScopeInfo _defaultStaticScope; + private readonly EncryptionScopeInfo _defaultDynamicScope; + private readonly HashSet _dynamicSecretAssemblyNames; + + public EncryptionScopeProvider(EncryptionScopeInfo defaultStaticScope, EncryptionScopeInfo defaultDynamicScope, HashSet dynamicSecretAssemblyNames) + { + _defaultStaticScope = defaultStaticScope; + _defaultDynamicScope = defaultDynamicScope; + _dynamicSecretAssemblyNames = dynamicSecretAssemblyNames; + } + + public EncryptionScopeInfo GetScope(ModuleDef module) + { + if (_dynamicSecretAssemblyNames.Contains(module.Assembly.Name)) + { + return _defaultDynamicScope; + } + else + { + return _defaultStaticScope; + } + } + + public bool IsDynamicSecretAssembly(ModuleDef module) + { + return _dynamicSecretAssemblyNames.Contains(module.Assembly.Name); + } + } + + public class ObfuscationPassContext + { + public static ObfuscationPassContext Current { get; set; } + + public CoreSettingsFacade coreSettings; + + public GroupByModuleEntityManager moduleEntityManager; + + public AssemblyCache assemblyCache; + public List modulesToObfuscate; + public List allObfuscationRelativeModules; + public ObfuzIgnoreScopeComputeCache obfuzIgnoreScopeComputeCache; + + public ObfuscationMethodWhitelist whiteList; + public ConfigurablePassPolicy passPolicy; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta new file mode 100644 index 00000000..827ddc44 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscationPassContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 196d07f0366ce4b46bf0333fa4918d40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs new file mode 100644 index 00000000..95a5f83f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs @@ -0,0 +1,352 @@ +using dnlib.DotNet; +using Obfuz.Data; +using Obfuz.Emit; +using Obfuz.EncryptionVM; +using Obfuz.ObfusPasses.CleanUp; +using Obfuz.ObfusPasses.Instinct; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.Unity; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace Obfuz +{ + + public class Obfuscator + { + private readonly CoreSettingsFacade _coreSettings; + private readonly List _allObfuscationRelativeAssemblyNames; + private readonly HashSet _assembliesUsingDynamicSecretKeys; + private readonly CombinedAssemblyResolver _assemblyResolver; + + private readonly ConfigurablePassPolicy _passPolicy; + + private readonly Pipeline _pipeline1 = new Pipeline(); + private readonly Pipeline _pipeline2 = new Pipeline(); + + private ObfuscationPassContext _ctx; + + public Obfuscator(ObfuscatorBuilder builder) + { + CheckSettings(builder.CoreSettingsFacade); + _coreSettings = builder.CoreSettingsFacade; + + _allObfuscationRelativeAssemblyNames = _coreSettings.assembliesToObfuscate + .Concat(_coreSettings.nonObfuscatedButReferencingObfuscatedAssemblies) + .ToList(); + _assembliesUsingDynamicSecretKeys = new HashSet(_coreSettings.assembliesUsingDynamicSecretKeys); + + _assemblyResolver = new CombinedAssemblyResolver(new PathAssemblyResolver(_coreSettings.assemblySearchPaths.ToArray()), new UnityProjectManagedAssemblyResolver(_coreSettings.buildTarget)); + _passPolicy = new ConfigurablePassPolicy(_coreSettings.assembliesToObfuscate, _coreSettings.enabledObfuscationPasses, _coreSettings.obfuscationPassRuleConfigFiles); + + _pipeline1.AddPass(new InstinctPass()); + foreach (var pass in _coreSettings.obfuscationPasses) + { + if (pass is SymbolObfusPass symbolObfusPass) + { + _pipeline2.AddPass(pass); + } + else + { + _pipeline1.AddPass(pass); + } + } + _pipeline1.AddPass(new CleanUpInstructionPass()); + _pipeline2.AddPass(new RemoveObfuzAttributesPass()); + } + + private void CheckSettings(CoreSettingsFacade settings) + { + var totalAssemblies = new HashSet(); + foreach (var assName in settings.assembliesToObfuscate) + { + if (string.IsNullOrWhiteSpace(assName)) + { + throw new Exception($"the name of some assembly in assembliesToObfuscate is empty! Please check your settings."); + } + if (!totalAssemblies.Add(assName)) + { + throw new Exception($"the name of assembly `{assName}` in assembliesToObfuscate is duplicated! Please check your settings."); + } + } + foreach (var assName in settings.nonObfuscatedButReferencingObfuscatedAssemblies) + { + if (string.IsNullOrWhiteSpace(assName)) + { + throw new Exception($"the name of some assembly in nonObfuscatedButReferencingObfuscatedAssemblies is empty! Please check your settings."); + } + if (!totalAssemblies.Add(assName)) + { + throw new Exception($"the name of assembly `{assName}` in nonObfuscatedButReferencingObfuscatedAssemblies is duplicated! Please check your settings."); + } + } + } + + public void Run() + { + Debug.Log($"Obfuscator begin"); + var sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + FileUtil.RecreateDir(_coreSettings.obfuscatedAssemblyOutputPath); + FileUtil.RecreateDir(_coreSettings.obfuscatedAssemblyTempOutputPath); + RunPipeline(_pipeline1); + _assemblyResolver.InsertFirst(new PathAssemblyResolver(_coreSettings.obfuscatedAssemblyTempOutputPath)); + RunPipeline(_pipeline2); + FileUtil.CopyDir(_coreSettings.obfuscatedAssemblyTempOutputPath, _coreSettings.obfuscatedAssemblyOutputPath, true); + sw.Stop(); + Debug.Log($"Obfuscator end. cost time: {sw.ElapsedMilliseconds} ms"); + } + + private void RunPipeline(Pipeline pipeline) + { + if (pipeline.Empty) + { + return; + } + OnPreObfuscation(pipeline); + DoObfuscation(pipeline); + OnPostObfuscation(pipeline); + } + + private IEncryptor CreateEncryptionVirtualMachine(byte[] secretKey) + { + var vmCreator = new VirtualMachineCreator(_coreSettings.encryptionVmGenerationSecretKey); + var vm = vmCreator.CreateVirtualMachine(_coreSettings.encryptionVmOpCodeCount); + var vmGenerator = new VirtualMachineCodeGenerator(vm); + + string encryptionVmCodeFile = _coreSettings.encryptionVmCodeFile; + if (!File.Exists(encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{encryptionVmCodeFile}` not exists! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (!vmGenerator.ValidateMatch(encryptionVmCodeFile)) + { + throw new Exception($"EncryptionVm CodeFile:`{encryptionVmCodeFile}` not match with encryptionVM settings! Please run `Obfuz/GenerateVm` to update it!"); + } + var vms = new VirtualMachineSimulator(vm, secretKey); + + var generatedVmTypes = ReflectionUtil.FindTypesInCurrentAppDomain("Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine"); + if (generatedVmTypes.Count == 0) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine not found in any assembly! Please run `Obfuz/GenerateVm` to generate it!"); + } + if (generatedVmTypes.Count > 1) + { + throw new Exception($"class Obfuz.EncryptionVM.GeneratedEncryptionVirtualMachine found in multiple assemblies! Please retain only one!"); + } + + var gvmInstance = (IEncryptor)Activator.CreateInstance(generatedVmTypes[0], new object[] { secretKey }); + + VerifyVm(vm, vms, gvmInstance); + + return vms; + } + + private void VerifyVm(VirtualMachine vm, VirtualMachineSimulator vms, IEncryptor gvm) + { + int testInt = 11223344; + long testLong = 1122334455667788L; + float testFloat = 1234f; + double testDouble = 1122334455.0; + string testString = "hello,world"; + for (int i = 0; i < vm.opCodes.Length; i++) + { + int ops = i * vm.opCodes.Length + i; + //int salt = i; + //int ops = -1135538782; + int salt = -879409147; + { + int encryptedIntOfVms = vms.Encrypt(testInt, ops, salt); + int decryptedIntOfVms = vms.Decrypt(encryptedIntOfVms, ops, salt); + if (decryptedIntOfVms != testInt) + { + throw new Exception($"VirtualMachineSimulator decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedIntOfVms}"); + } + int encryptedValueOfGvm = gvm.Encrypt(testInt, ops, salt); + int decryptedValueOfGvm = gvm.Decrypt(encryptedValueOfGvm, ops, salt); + if (encryptedValueOfGvm != encryptedIntOfVms) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testInt} encryptedValue VirtualMachineSimulator:{encryptedIntOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}"); + } + if (decryptedValueOfGvm != testInt) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt failed! opCode:{i}, originalValue:{testInt} decryptedValue:{decryptedValueOfGvm}"); + } + } + { + long encryptedLongOfVms = vms.Encrypt(testLong, ops, salt); + long decryptedLongOfVms = vms.Decrypt(encryptedLongOfVms, ops, salt); + if (decryptedLongOfVms != testLong) + { + throw new Exception($"VirtualMachineSimulator decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedLongOfVms}"); + } + long encryptedValueOfGvm = gvm.Encrypt(testLong, ops, salt); + long decryptedValueOfGvm = gvm.Decrypt(encryptedValueOfGvm, ops, salt); + if (encryptedValueOfGvm != encryptedLongOfVms) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testLong} encryptedValue VirtualMachineSimulator:{encryptedLongOfVms} GeneratedEncryptionVirtualMachine:{encryptedValueOfGvm}"); + } + if (decryptedValueOfGvm != testLong) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt long failed! opCode:{i}, originalValue:{testLong} decryptedValue:{decryptedValueOfGvm}"); + } + } + { + float encryptedFloatOfVms = vms.Encrypt(testFloat, ops, salt); + float decryptedFloatOfVms = vms.Decrypt(encryptedFloatOfVms, ops, salt); + if (decryptedFloatOfVms != testFloat) + { + throw new Exception("encryptedFloat not match"); + } + float encryptedValueOfGvm = gvm.Encrypt(testFloat, ops, salt); + float decryptedValueOfGvm = gvm.Decrypt(encryptedFloatOfVms, ops, salt); + if (encryptedFloatOfVms != encryptedValueOfGvm) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testFloat} encryptedValue"); + } + if (decryptedValueOfGvm != testFloat) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testFloat}"); + } + } + { + double encryptedFloatOfVms = vms.Encrypt(testDouble, ops, salt); + double decryptedFloatOfVms = vms.Decrypt(encryptedFloatOfVms, ops, salt); + if (decryptedFloatOfVms != testDouble) + { + throw new Exception("encryptedFloat not match"); + } + double encryptedValueOfGvm = gvm.Encrypt(testDouble, ops, salt); + double decryptedValueOfGvm = gvm.Decrypt(encryptedFloatOfVms, ops, salt); + if (encryptedFloatOfVms != encryptedValueOfGvm) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testDouble} encryptedValue"); + } + if (decryptedValueOfGvm != testDouble) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt float failed! opCode:{i}, originalValue:{testDouble}"); + } + } + + { + byte[] encryptedStrOfVms = vms.Encrypt(testString, ops, salt); + string decryptedStrOfVms = vms.DecryptString(encryptedStrOfVms, 0, encryptedStrOfVms.Length, ops, salt); + if (decryptedStrOfVms != testString) + { + throw new Exception($"VirtualMachineSimulator decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfVms}"); + } + byte[] encryptedStrOfGvm = gvm.Encrypt(testString, ops, salt); + string decryptedStrOfGvm = gvm.DecryptString(encryptedStrOfGvm, 0, encryptedStrOfGvm.Length, ops, salt); + if (!encryptedStrOfGvm.SequenceEqual(encryptedStrOfVms)) + { + throw new Exception($"encryptedValue not match! opCode:{i}, originalValue:{testString} encryptedValue VirtualMachineSimulator:{encryptedStrOfVms} GeneratedEncryptionVirtualMachine:{encryptedStrOfGvm}"); + } + if (decryptedStrOfGvm != testString) + { + throw new Exception($"GeneratedEncryptionVirtualMachine decrypt string failed! opCode:{i}, originalValue:{testString} decryptedValue:{decryptedStrOfGvm}"); + } + } + } + } + + private EncryptionScopeInfo CreateEncryptionScope(byte[] byteSecret) + { + int[] intSecretKey = KeyGenerator.ConvertToIntKey(byteSecret); + IEncryptor encryption = CreateEncryptionVirtualMachine(byteSecret); + RandomCreator localRandomCreator = (seed) => new RandomWithKey(intSecretKey, _coreSettings.randomSeed ^ seed); + return new EncryptionScopeInfo(encryption, localRandomCreator); + } + + private EncryptionScopeProvider CreateEncryptionScopeProvider() + { + var defaultStaticScope = CreateEncryptionScope(_coreSettings.defaultStaticSecretKey); + var defaultDynamicScope = CreateEncryptionScope(_coreSettings.defaultDynamicSecretKey); + foreach (string dynamicAssName in _assembliesUsingDynamicSecretKeys) + { + if (!_coreSettings.assembliesToObfuscate.Contains(dynamicAssName)) + { + throw new Exception($"Dynamic secret assembly `{dynamicAssName}` should be in the assembliesToObfuscate list!"); + } + } + return new EncryptionScopeProvider(defaultStaticScope, defaultDynamicScope, _assembliesUsingDynamicSecretKeys); + } + + private void OnPreObfuscation(Pipeline pipeline) + { + AssemblyCache assemblyCache = new AssemblyCache(_assemblyResolver); + List modulesToObfuscate = new List(); + List allObfuscationRelativeModules = new List(); + LoadAssemblies(assemblyCache, modulesToObfuscate, allObfuscationRelativeModules); + + EncryptionScopeProvider encryptionScopeProvider = CreateEncryptionScopeProvider(); + var moduleEntityManager = new GroupByModuleEntityManager() + { + EncryptionScopeProvider = encryptionScopeProvider, + }; + var obfuzIgnoreScopeComputeCache = new ObfuzIgnoreScopeComputeCache(); + _ctx = new ObfuscationPassContext + { + coreSettings = _coreSettings, + assemblyCache = assemblyCache, + modulesToObfuscate = modulesToObfuscate, + allObfuscationRelativeModules = allObfuscationRelativeModules, + + moduleEntityManager = moduleEntityManager, + + obfuzIgnoreScopeComputeCache = obfuzIgnoreScopeComputeCache, + + whiteList = new ObfuscationMethodWhitelist(obfuzIgnoreScopeComputeCache, new BurstCompileComputeCache(modulesToObfuscate, allObfuscationRelativeModules)), + passPolicy = _passPolicy, + }; + ObfuscationPassContext.Current = _ctx; + pipeline.Start(); + } + + private void LoadAssemblies(AssemblyCache assemblyCache, List modulesToObfuscate, List allObfuscationRelativeModules) + { + foreach (string assName in _allObfuscationRelativeAssemblyNames) + { + ModuleDefMD mod = assemblyCache.TryLoadModule(assName); + if (mod == null) + { + Debug.Log($"assembly: {assName} not found! ignore."); + continue; + } + if (_coreSettings.assembliesToObfuscate.Contains(assName)) + { + modulesToObfuscate.Add(mod); + } + allObfuscationRelativeModules.Add(mod); + } + } + + private void WriteAssemblies() + { + foreach (ModuleDef mod in _ctx.allObfuscationRelativeModules) + { + string assNameWithExt = mod.Name; + string outputFile = $"{_coreSettings.obfuscatedAssemblyTempOutputPath}/{assNameWithExt}"; + mod.Write(outputFile); + Debug.Log($"save module. name:{mod.Assembly.Name} output:{outputFile}"); + } + } + + private void DoObfuscation(Pipeline pipeline) + { + pipeline.Run(); + } + + private void OnPostObfuscation(Pipeline pipeline) + { + pipeline.Stop(); + + _ctx.moduleEntityManager.Done(); + _ctx.moduleEntityManager.Done(); + WriteAssemblies(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta new file mode 100644 index 00000000..08291f34 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca9c4e108bde2184885e599f2bd19a00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs new file mode 100644 index 00000000..35e29ee7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs @@ -0,0 +1,207 @@ +using Obfuz.EncryptionVM; +using Obfuz.ObfusPasses; +using Obfuz.ObfusPasses.CallObfus; +using Obfuz.ObfusPasses.ConstEncrypt; +using Obfuz.ObfusPasses.ControlFlowObfus; +using Obfuz.ObfusPasses.EvalStackObfus; +using Obfuz.ObfusPasses.ExprObfus; +using Obfuz.ObfusPasses.FieldEncrypt; +using Obfuz.ObfusPasses.SymbolObfus; +using Obfuz.Settings; +using Obfuz.Utils; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; + +namespace Obfuz +{ + + public class CoreSettingsFacade + { + public BuildTarget buildTarget; + + public byte[] defaultStaticSecretKey; + public byte[] defaultDynamicSecretKey; + public List assembliesUsingDynamicSecretKeys; + public int randomSeed; + + public string encryptionVmGenerationSecretKey; + public int encryptionVmOpCodeCount; + public string encryptionVmCodeFile; + + public List assembliesToObfuscate; + public List nonObfuscatedButReferencingObfuscatedAssemblies; + public List assemblySearchPaths; + public string obfuscatedAssemblyOutputPath; + public string obfuscatedAssemblyTempOutputPath; + + public ObfuscationPassType enabledObfuscationPasses; + public List obfuscationPassRuleConfigFiles; + public List obfuscationPasses; + } + + public class ObfuscatorBuilder + { + private CoreSettingsFacade _coreSettingsFacade; + + public CoreSettingsFacade CoreSettingsFacade => _coreSettingsFacade; + + public void InsertTopPriorityAssemblySearchPaths(List assemblySearchPaths) + { + _coreSettingsFacade.assemblySearchPaths.InsertRange(0, assemblySearchPaths); + } + + public ObfuscatorBuilder AddPass(IObfuscationPass pass) + { + _coreSettingsFacade.obfuscationPasses.Add(pass); + return this; + } + + public Obfuscator Build() + { + return new Obfuscator(this); + } + + public static List BuildUnityAssemblySearchPaths(bool searchPathIncludeUnityEditorDll = false) + { + string applicationContentsPath = EditorApplication.applicationContentsPath; + var searchPaths = new List + { +#if UNITY_2021_1_OR_NEWER +#if UNITY_STANDALONE_WIN || (UNITY_EDITOR_WIN && UNITY_SERVER) || UNITY_WSA || UNITY_LUMIN + "MonoBleedingEdge/lib/mono/unityaot-win32", + "MonoBleedingEdge/lib/mono/unityaot-win32/Facades", +#elif UNITY_STANDALONE_OSX || (UNITY_EDITOR_OSX && UNITY_SERVER) || UNITY_IOS || UNITY_TVOS + "MonoBleedingEdge/lib/mono/unityaot-macos", + "MonoBleedingEdge/lib/mono/unityaot-macos/Facades", +#else + "MonoBleedingEdge/lib/mono/unityaot-linux", + "MonoBleedingEdge/lib/mono/unityaot-linux/Facades", +#endif +#else + "MonoBleedingEdge/lib/mono/unityaot", + "MonoBleedingEdge/lib/mono/unityaot/Facades", +#endif + +#if UNITY_STANDALONE_WIN || (UNITY_EDITOR_WIN && UNITY_SERVER) + "PlaybackEngines/windowsstandalonesupport/Variations/il2cpp/Managed", +#elif UNITY_STANDALONE_OSX || (UNITY_EDITOR_OSX && UNITY_SERVER) + "PlaybackEngines/MacStandaloneSupport/Variations/il2cpp/Managed", +#elif UNITY_STANDALONE_LINUX || (UNITY_EDITOR_LINUX && UNITY_SERVER) + "PlaybackEngines/LinuxStandaloneSupport/Variations/il2cpp/Managed", +#elif UNITY_ANDROID + "PlaybackEngines/AndroidPlayer/Variations/il2cpp/Managed", +#elif UNITY_IOS + "PlaybackEngines/iOSSupport/Variations/il2cpp/Managed", +#elif UNITY_MINIGAME || UNITY_WEIXINMINIGAME +#if TUANJIE_1_1_OR_NEWER + "PlaybackEngines/WeixinMiniGameSupport/Variations/il2cpp/nondevelopment/Data/Managed", +#else + "PlaybackEngines/WeixinMiniGameSupport/Variations/nondevelopment/Data/Managed", +#endif +#elif UNITY_OPENHARMONY + "PlaybackEngines/OpenHarmonyPlayer/Variations/il2cpp/Managed", +#elif UNITY_WEBGL + "PlaybackEngines/WebGLSupport/Variations/nondevelopment/Data/Managed", +#elif UNITY_TVOS + "PlaybackEngines/AppleTVSupport/Variations/il2cpp/Managed", +#elif UNITY_WSA + "PlaybackEngines/WSASupport/Variations/il2cpp/Managed", +#elif UNITY_LUMIN + "PlaybackEngines/LuminSupport/Variations/il2cpp/Managed", +#else +#error "Unsupported platform, please report to us" +#endif + }; + + if (searchPathIncludeUnityEditorDll) + { + searchPaths.Add("Managed/UnityEngine"); + } + + var resultPaths = new List(); + foreach (var path in searchPaths) + { + string candidatePath1 = Path.Combine(applicationContentsPath, path); + if (Directory.Exists(candidatePath1)) + { + resultPaths.Add(candidatePath1); + } + if (path.StartsWith("PlaybackEngines")) + { + string candidatePath2 = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(applicationContentsPath)), path); + if (Directory.Exists(candidatePath2)) + { + resultPaths.Add(candidatePath2); + } + } + } + return resultPaths; + } + + public static ObfuscatorBuilder FromObfuzSettings(ObfuzSettings settings, BuildTarget target, bool searchPathIncludeUnityEditorInstallLocation, bool searchPathIncludeUnityEditorDll = false) + { + List searchPaths = searchPathIncludeUnityEditorInstallLocation ? + BuildUnityAssemblySearchPaths(searchPathIncludeUnityEditorDll).Concat(settings.assemblySettings.additionalAssemblySearchPaths).ToList() + : settings.assemblySettings.additionalAssemblySearchPaths.ToList(); + foreach (var path in searchPaths) + { + bool exists = Directory.Exists(path); + UnityEngine.Debug.Log($"search path:{path} exists:{exists}"); + } + var builder = new ObfuscatorBuilder + { + _coreSettingsFacade = new CoreSettingsFacade() + { + buildTarget = target, + defaultStaticSecretKey = KeyGenerator.GenerateKey(settings.secretSettings.defaultStaticSecretKey, VirtualMachine.SecretKeyLength), + defaultDynamicSecretKey = KeyGenerator.GenerateKey(settings.secretSettings.defaultDynamicSecretKey, VirtualMachine.SecretKeyLength), + assembliesUsingDynamicSecretKeys = settings.secretSettings.assembliesUsingDynamicSecretKeys.ToList(), + randomSeed = settings.secretSettings.randomSeed, + encryptionVmGenerationSecretKey = settings.encryptionVMSettings.codeGenerationSecretKey, + encryptionVmOpCodeCount = settings.encryptionVMSettings.encryptionOpCodeCount, + encryptionVmCodeFile = settings.encryptionVMSettings.codeOutputPath, + assembliesToObfuscate = settings.assemblySettings.GetAssembliesToObfuscate(), + nonObfuscatedButReferencingObfuscatedAssemblies = settings.assemblySettings.nonObfuscatedButReferencingObfuscatedAssemblies.ToList(), + assemblySearchPaths = searchPaths, + obfuscatedAssemblyOutputPath = settings.GetObfuscatedAssemblyOutputPath(target), + obfuscatedAssemblyTempOutputPath = settings.GetObfuscatedAssemblyTempOutputPath(target), + enabledObfuscationPasses = settings.obfuscationPassSettings.enabledPasses, + obfuscationPassRuleConfigFiles = settings.obfuscationPassSettings.ruleFiles?.ToList() ?? new List(), + obfuscationPasses = new List(), + }, + }; + ObfuscationPassType obfuscationPasses = settings.obfuscationPassSettings.enabledPasses; + if (obfuscationPasses.HasFlag(ObfuscationPassType.ConstEncrypt)) + { + builder.AddPass(new ConstEncryptPass(settings.constEncryptSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.ExprObfus)) + { + builder.AddPass(new ExprObfusPass(settings.exprObfusSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.EvalStackObfus)) + { + builder.AddPass(new EvalStackObfusPass(settings.evalStackObfusSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.FieldEncrypt)) + { + builder.AddPass(new FieldEncryptPass(settings.fieldEncryptSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.CallObfus)) + { + builder.AddPass(new CallObfusPass(settings.callObfusSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.ControlFlowObfus)) + { + builder.AddPass(new ControlFlowObfusPass(settings.controlFlowObfusSettings.ToFacade())); + } + if (obfuscationPasses.HasFlag(ObfuscationPassType.SymbolObfus)) + { + builder.AddPass(new SymbolObfusPass(settings.symbolObfusSettings.ToFacade())); + } + return builder; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta new file mode 100644 index 00000000..261993be --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/ObfuscatorBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77e279242d764ed4d996db96f35e8570 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef new file mode 100644 index 00000000..e1b3933b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Obfuz.Editor", + "rootNamespace": "", + "references": [ + "GUID:4140bd2e2764f1f47ab93125ecb61942" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta new file mode 100644 index 00000000..d010c098 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Obfuz.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 66e09fc524ec6594b8d6ca1d91aa1a41 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs new file mode 100644 index 00000000..e0869b41 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Obfuz +{ + public class Pipeline + { + private readonly List _passes = new List(); + + public bool Empty => _passes.Count == 0; + + public Pipeline AddPass(IObfuscationPass pass) + { + _passes.Add(pass); + return this; + } + + public void Start() + { + foreach (var pass in _passes) + { + pass.Start(); + } + } + + public void Stop() + { + + foreach (var pass in _passes) + { + pass.Stop(); + } + } + + public void Run() + { + var sw = new Stopwatch(); + foreach (var pass in _passes) + { + sw.Restart(); + pass.Process(); + sw.Stop(); + UnityEngine.Debug.Log($"Pass: {pass.GetType().Name} process cost time: {sw.ElapsedMilliseconds}ms"); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta new file mode 100644 index 00000000..fd3558ce --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Pipeline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0915452219e923d428b9a408cf413844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings.meta new file mode 100644 index 00000000..64d3c0a6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4939d1f80df50b34c85ffc8fbe530887 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs new file mode 100644 index 00000000..97923519 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs @@ -0,0 +1,41 @@ +using Obfuz.Editor; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class AssemblySettings + { + + [Tooltip("name of assemblies to obfuscate, please don't add 'Obfuz.Runtime'")] + public string[] assembliesToObfuscate; + + [Tooltip("name of assemblies not obfuscated but reference assemblies to obfuscated ")] + public string[] nonObfuscatedButReferencingObfuscatedAssemblies; + + [Tooltip("additional assembly search paths")] + public string[] additionalAssemblySearchPaths; + + [Tooltip("obfuscate Obfuz.Runtime")] + public bool obfuscateObfuzRuntime = true; + + public List GetAssembliesToObfuscate() + { + var asses = new List(assembliesToObfuscate ?? Array.Empty()); + if (obfuscateObfuzRuntime && !asses.Contains(ConstValues.ObfuzRuntimeAssemblyName)) + { + asses.Add(ConstValues.ObfuzRuntimeAssemblyName); + } + return asses; + } + + public List GetObfuscationRelativeAssemblyNames() + { + var asses = GetAssembliesToObfuscate(); + asses.AddRange(nonObfuscatedButReferencingObfuscatedAssemblies ?? Array.Empty()); + return asses; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta new file mode 100644 index 00000000..8a139403 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/AssemblySettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 735c01fddc6f0c54a83e1feb9a60e13a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs new file mode 100644 index 00000000..8d167fad --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs @@ -0,0 +1,18 @@ +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class BuildPipelineSettings + { + [Tooltip("enable Obfuz")] + public bool enable = true; + + [Tooltip("callback order of LinkXmlProcessor")] + public int linkXmlProcessCallbackOrder = 10000; + + [Tooltip("callback order of ObfuscationProcess")] + public int obfuscationProcessCallbackOrder = 10000; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs.meta new file mode 100644 index 00000000..64ce1ddb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/BuildPipelineSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68737b215ecfe344a93d56007e186432 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs new file mode 100644 index 00000000..a7ee139a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.Settings +{ + public enum ProxyMode + { + Dispatch, + Delegate, + } + + public class CallObfuscationSettingsFacade + { + public ProxyMode proxyMode; + public int obfuscationLevel; + public int maxProxyMethodCountPerDispatchMethod; + public bool obfuscateCallToMethodInMscorlib; + public List ruleFiles; + } + + [Serializable] + public class CallObfuscationSettings + { + public ProxyMode proxyMode = ProxyMode.Dispatch; + + [Tooltip("The obfuscation level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int obfuscationLevel = 1; + + [Tooltip("The maximum number of proxy methods that can be generated per dispatch method. This helps to limit the complexity of the generated code and improve performance.")] + public int maxProxyMethodCountPerDispatchMethod = 100; + + [Tooltip("Whether to obfuscate calls to methods in mscorlib. Enable this option will impact performance.")] + public bool obfuscateCallToMethodInMscorlib; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public CallObfuscationSettingsFacade ToFacade() + { + return new CallObfuscationSettingsFacade + { + proxyMode = proxyMode, + obfuscationLevel = obfuscationLevel, + maxProxyMethodCountPerDispatchMethod = maxProxyMethodCountPerDispatchMethod, + obfuscateCallToMethodInMscorlib = obfuscateCallToMethodInMscorlib, + ruleFiles = ruleFiles?.ToList() ?? new List(), + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta new file mode 100644 index 00000000..ce268f1e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/CallObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae4ddc19ecf8d9940b444f5b66c23efa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs new file mode 100644 index 00000000..6baffbc1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.Settings +{ + public class ConstEncryptionSettingsFacade + { + public int encryptionLevel; + public List ruleFiles; + } + + [Serializable] + public class ConstEncryptionSettings + { + [Tooltip("The encryption level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int encryptionLevel = 1; + + [Tooltip("config xml files")] + public string[] ruleFiles; + + public ConstEncryptionSettingsFacade ToFacade() + { + return new ConstEncryptionSettingsFacade + { + ruleFiles = ruleFiles?.ToList() ?? new List(), + encryptionLevel = encryptionLevel, + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta new file mode 100644 index 00000000..d5cef401 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ConstEncryptionSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b189f76d7da58e4b9403b4fe87265b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs new file mode 100644 index 00000000..67474b1b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.Settings +{ + + public class ControlFlowObfuscationSettingsFacade + { + public int minInstructionCountOfBasicBlockToObfuscate; + public List ruleFiles; + } + + [Serializable] + public class ControlFlowObfuscationSettings + { + public int minInstructionCountOfBasicBlockToObfuscate = 3; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public ControlFlowObfuscationSettingsFacade ToFacade() + { + return new ControlFlowObfuscationSettingsFacade + { + minInstructionCountOfBasicBlockToObfuscate = minInstructionCountOfBasicBlockToObfuscate, + ruleFiles = new List(ruleFiles ?? Array.Empty()), + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs.meta new file mode 100644 index 00000000..8d8b0f28 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ControlFlowObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d40d2fb33f081b2458505c7566b1bc8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs new file mode 100644 index 00000000..50159587 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs @@ -0,0 +1,18 @@ +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class EncryptionVMSettings + { + [Tooltip("secret key for generating encryption virtual machine source code")] + public string codeGenerationSecretKey = "Obfuz"; + + [Tooltip("encryption OpCode count, should be power of 2 and >= 64")] + public int encryptionOpCodeCount = 256; + + [Tooltip("encryption virtual machine source code output path")] + public string codeOutputPath = "Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs"; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta new file mode 100644 index 00000000..0257381b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EncryptionVMSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0e626d510710c540bdc36b4dca93340 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs new file mode 100644 index 00000000..b880f534 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.Settings +{ + + public class EvalStackObfuscationSettingsFacade + { + public List ruleFiles; + } + + [Serializable] + public class EvalStackObfuscationSettings + { + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public EvalStackObfuscationSettingsFacade ToFacade() + { + return new EvalStackObfuscationSettingsFacade + { + ruleFiles = new List(ruleFiles ?? Array.Empty()), + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs.meta new file mode 100644 index 00000000..8e4d8d59 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/EvalStackObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c985896b3a4ef84292cb0cfbc79dc10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs new file mode 100644 index 00000000..c4d0f750 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.Settings +{ + + public class ExprObfuscationSettingsFacade + { + public List ruleFiles; + } + + [Serializable] + public class ExprObfuscationSettings + { + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public ExprObfuscationSettingsFacade ToFacade() + { + return new ExprObfuscationSettingsFacade + { + ruleFiles = new List(ruleFiles ?? Array.Empty()), + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs.meta new file mode 100644 index 00000000..55fb66c9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ExprObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d63d19f2b1a9f7c4b9812577d215c367 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs new file mode 100644 index 00000000..460eca1d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.Settings +{ + public class FieldEncryptionSettingsFacade + { + public int encryptionLevel; + public List ruleFiles; + } + + [Serializable] + public class FieldEncryptionSettings + { + [Tooltip("The encryption level for the obfuscation. Higher levels provide more security but may impact performance.")] + [Range(1, 4)] + public int encryptionLevel = 1; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public FieldEncryptionSettingsFacade ToFacade() + { + return new FieldEncryptionSettingsFacade + { + ruleFiles = ruleFiles?.ToList() ?? new List(), + encryptionLevel = encryptionLevel, + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta new file mode 100644 index 00000000..fbc0e2ed --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/FieldEncryptionSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56cff1f5683d9114e8f13cee917ea582 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs new file mode 100644 index 00000000..b17748f1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs @@ -0,0 +1,41 @@ +using System; + +namespace Obfuz.Settings +{ + public enum GarbageCodeType + { + None, + Config, + UI, + } + + [Serializable] + public class GarbageCodeGenerationTask + { + public int codeGenerationRandomSeed; + + public string classNamespace = "__GarbageCode"; + + public string classNamePrefix = "__GeneratedGarbageClass"; + + public int classCount = 100; + + public int methodCountPerClass = 10; + + public int fieldCountPerClass = 50; + + public GarbageCodeType garbageCodeType = GarbageCodeType.Config; + + public string outputPath = "Assets/Obfuz/GarbageCode"; + } + + [Serializable] + public class GarbageCodeGenerationSettings + { + public string codeGenerationSecret = "Garbage Code"; + + public GarbageCodeGenerationTask defaultTask; + + public GarbageCodeGenerationTask[] additionalTasks; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs.meta new file mode 100644 index 00000000..326444eb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/GarbageCodeGenerationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 220b44e477cfa2848bd287c38db4fd21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs new file mode 100644 index 00000000..b3f57020 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs @@ -0,0 +1,10 @@ +namespace Obfuz.Settings +{ + public enum ObfuscationLevel + { + None = 0, + Basic = 1, + Advanced = 2, + MostAdvanced = 3 + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs.meta new file mode 100644 index 00000000..c6567f4e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationLevel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd8e1281c6c9bcd419fecc67980cb673 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs new file mode 100644 index 00000000..837c8b0c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs @@ -0,0 +1,16 @@ +using Obfuz.ObfusPasses; +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class ObfuscationPassSettings + { + [Tooltip("enable obfuscation pass")] + public ObfuscationPassType enabledPasses = ObfuscationPassType.All; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta new file mode 100644 index 00000000..147fed67 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuscationPassSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9cb41a6fb7293ce47807e432d80f2b2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs new file mode 100644 index 00000000..19fbe3cb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs @@ -0,0 +1,122 @@ +using System.IO; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace Obfuz.Settings +{ + + public class ObfuzSettings : ScriptableObject + { + [Tooltip("build pipeline settings")] + public BuildPipelineSettings buildPipelineSettings; + + [Tooltip("assembly settings")] + public AssemblySettings assemblySettings; + + [Tooltip("obfuscation pass settings")] + public ObfuscationPassSettings obfuscationPassSettings; + + [Tooltip("secret settings")] + public SecretSettings secretSettings; + + [Tooltip("encryption virtual machine settings")] + public EncryptionVMSettings encryptionVMSettings; + + [Tooltip("symbol obfuscation settings")] + public SymbolObfuscationSettings symbolObfusSettings; + + [Tooltip("const encryption settings")] + public ConstEncryptionSettings constEncryptSettings; + + [Tooltip("eval stack obfuscation settings")] + public EvalStackObfuscationSettings evalStackObfusSettings; + + [Tooltip("field encryption settings")] + public FieldEncryptionSettings fieldEncryptSettings; + + [Tooltip("call obfuscation settings")] + public CallObfuscationSettings callObfusSettings; + + [Tooltip("expression obfuscation settings")] + public ExprObfuscationSettings exprObfusSettings; + + [Tooltip("control flow obfuscation settings")] + public ControlFlowObfuscationSettings controlFlowObfusSettings; + + [Tooltip("garbage code generator settings")] + public GarbageCodeGenerationSettings garbageCodeGenerationSettings; + + [Tooltip("polymorphic dll settings")] + public PolymorphicDllSettings polymorphicDllSettings; + + public string ObfuzRootDir => $"Library/Obfuz"; + + public string GetObfuscatedAssemblyOutputPath(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/ObfuscatedAssemblies"; + } + + public string GetOriginalAssemblyBackupDir(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/OriginalAssemblies"; + } + + public string GetObfuscatedAssemblyTempOutputPath(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/TempObfuscatedAssemblies"; + } + + public string GetObfuscatedLinkXmlPath(BuildTarget target) + { + return $"{ObfuzRootDir}/{target}/link.xml"; + } + + private static ObfuzSettings s_Instance; + + public static ObfuzSettings Instance + { + get + { + if (!s_Instance) + { + LoadOrCreate(); + } + return s_Instance; + } + } + + protected static string SettingsPath => "ProjectSettings/Obfuz.asset"; + + private static ObfuzSettings LoadOrCreate() + { + string filePath = SettingsPath; + var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath); + //Debug.Log($"typeof arr:{arr?.GetType()} arr[0]:{(arr != null && arr.Length > 0 ? arr[0].GetType(): null)}"); + + if (arr != null && arr.Length > 0 && arr[0] is ObfuzSettings obfuzSettings) + { + s_Instance = obfuzSettings; + } + else + { + s_Instance = s_Instance ?? CreateInstance(); + } + return s_Instance; + } + + public static void Save() + { + if (!s_Instance) + { + return; + } + + string filePath = SettingsPath; + string directoryName = Path.GetDirectoryName(filePath); + Directory.CreateDirectory(directoryName); + UnityEngine.Object[] obj = new ObfuzSettings[1] { s_Instance }; + InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, true); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta new file mode 100644 index 00000000..8bad58fc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c414eef017e565c4db1442ec64ec52fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs new file mode 100644 index 00000000..2b0afced --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs @@ -0,0 +1,123 @@ +using UnityEditor; +using UnityEngine.UIElements; + +namespace Obfuz.Settings +{ + public class ObfuzSettingsProvider : SettingsProvider + { + + private static ObfuzSettingsProvider s_provider; + + [SettingsProvider] + public static SettingsProvider CreateMyCustomSettingsProvider() + { + if (s_provider == null) + { + s_provider = new ObfuzSettingsProvider(); + using (var so = new SerializedObject(ObfuzSettings.Instance)) + { + s_provider.keywords = GetSearchKeywordsFromSerializedObject(so); + } + } + return s_provider; + } + + + private SerializedObject _serializedObject; + private SerializedProperty _buildPipelineSettings; + + private SerializedProperty _assemblySettings; + private SerializedProperty _obfuscationPassSettings; + private SerializedProperty _secretSettings; + private SerializedProperty _encryptionVMSettings; + + private SerializedProperty _symbolObfusSettings; + private SerializedProperty _constEncryptSettings; + private SerializedProperty _evalStackObfusSettings; + private SerializedProperty _fieldEncryptSettings; + private SerializedProperty _callObfusSettings; + private SerializedProperty _exprObfusSettings; + private SerializedProperty _controlFlowObfusSettings; + + private SerializedProperty _garbageCodeGenerationSettings; + + private SerializedProperty _polymorphicDllSettings; + + public ObfuzSettingsProvider() : base("Project/Obfuz", SettingsScope.Project) + { + } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + InitGUI(); + } + + public override void OnDeactivate() + { + base.OnDeactivate(); + ObfuzSettings.Save(); + } + + private void InitGUI() + { + var setting = ObfuzSettings.Instance; + _serializedObject?.Dispose(); + _serializedObject = new SerializedObject(setting); + _buildPipelineSettings = _serializedObject.FindProperty("buildPipelineSettings"); + + _assemblySettings = _serializedObject.FindProperty("assemblySettings"); + _obfuscationPassSettings = _serializedObject.FindProperty("obfuscationPassSettings"); + _secretSettings = _serializedObject.FindProperty("secretSettings"); + + _encryptionVMSettings = _serializedObject.FindProperty("encryptionVMSettings"); + + _symbolObfusSettings = _serializedObject.FindProperty("symbolObfusSettings"); + _constEncryptSettings = _serializedObject.FindProperty("constEncryptSettings"); + _evalStackObfusSettings = _serializedObject.FindProperty("evalStackObfusSettings"); + _exprObfusSettings = _serializedObject.FindProperty("exprObfusSettings"); + _fieldEncryptSettings = _serializedObject.FindProperty("fieldEncryptSettings"); + _callObfusSettings = _serializedObject.FindProperty("callObfusSettings"); + _controlFlowObfusSettings = _serializedObject.FindProperty("controlFlowObfusSettings"); + + _garbageCodeGenerationSettings = _serializedObject.FindProperty("garbageCodeGenerationSettings"); + + _polymorphicDllSettings = _serializedObject.FindProperty("polymorphicDllSettings"); + } + + public override void OnGUI(string searchContext) + { + if (_serializedObject == null || !_serializedObject.targetObject) + { + InitGUI(); + } + _serializedObject.Update(); + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.PropertyField(_buildPipelineSettings); + + EditorGUILayout.PropertyField(_assemblySettings); + EditorGUILayout.PropertyField(_obfuscationPassSettings); + EditorGUILayout.PropertyField(_secretSettings); + + EditorGUILayout.PropertyField(_encryptionVMSettings); + + EditorGUILayout.PropertyField(_symbolObfusSettings); + EditorGUILayout.PropertyField(_constEncryptSettings); + EditorGUILayout.PropertyField(_evalStackObfusSettings); + EditorGUILayout.PropertyField(_exprObfusSettings); + EditorGUILayout.PropertyField(_fieldEncryptSettings); + EditorGUILayout.PropertyField(_callObfusSettings); + EditorGUILayout.PropertyField(_controlFlowObfusSettings); + + EditorGUILayout.PropertyField(_garbageCodeGenerationSettings); + + EditorGUILayout.PropertyField(_polymorphicDllSettings); + + if (EditorGUI.EndChangeCheck()) + { + _serializedObject.ApplyModifiedProperties(); + ObfuzSettings.Save(); + } + } + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta new file mode 100644 index 00000000..471477b1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/ObfuzSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f020d09993a1aa41bae3258ec33d5fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs new file mode 100644 index 00000000..e2d1180e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs @@ -0,0 +1,18 @@ +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class PolymorphicDllSettings + { + [Tooltip("enable polymorphic DLL generation")] + public bool enable = true; + + [Tooltip("secret key for generating polymorphic DLL source code")] + public string codeGenerationSecretKey = "obfuz-polymorphic-key"; + + [Tooltip("disable load standard dotnet dll")] + public bool disableLoadStandardDll = false; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs.meta new file mode 100644 index 00000000..f1885893 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/PolymorphicDllSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecab9aab707d08949b2d602a4d61084a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs new file mode 100644 index 00000000..9a9f6947 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs @@ -0,0 +1,28 @@ +using System; +using UnityEngine; + +namespace Obfuz.Settings +{ + [Serializable] + public class SecretSettings + { + + [Tooltip("default static secret key")] + public string defaultStaticSecretKey = "Code Philosophy-Static"; + + [Tooltip("default dynamic secret key")] + public string defaultDynamicSecretKey = "Code Philosophy-Dynamic"; + + [Tooltip("default static secret key output path")] + public string staticSecretKeyOutputPath = $"Assets/Resources/Obfuz/defaultStaticSecretKey.bytes"; + + [Tooltip("default dynamic secret key output path")] + public string dynamicSecretKeyOutputPath = $"Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes"; + + [Tooltip("random seed")] + public int randomSeed = 0; + + [Tooltip("name of assemblies those use dynamic secret key")] + public string[] assembliesUsingDynamicSecretKeys; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta new file mode 100644 index 00000000..7e26a78a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SecretSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ac4fe2a6df113444b67412254452a00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs new file mode 100644 index 00000000..f661c4d7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs @@ -0,0 +1,70 @@ +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Obfuz.Settings +{ + public class SymbolObfuscationSettingsFacade + { + public bool debug; + public string obfuscatedNamePrefix; + public bool useConsistentNamespaceObfuscation; + public bool detectReflectionCompatibility; + public bool keepUnknownSymbolInSymbolMappingFile; + public string symbolMappingFile; + public List ruleFiles; + public List customRenamePolicyTypes; + } + + [Serializable] + public class SymbolObfuscationSettings + { + public bool debug; + + [Tooltip("prefix for obfuscated name to avoid name confliction with original name")] + public string obfuscatedNamePrefix = "$"; + + [Tooltip("obfuscate same namespace to one name")] + public bool useConsistentNamespaceObfuscation = true; + + [Tooltip("detect reflection compatibility, if true, will detect if the obfuscated name is compatibility with reflection, such as Type.GetType(), Enum.Parse(), etc.")] + public bool detectReflectionCompatibility = true; + + [Tooltip("keep unknown symbol in symbol mapping file, if false, unknown symbol will be removed from mapping file")] + public bool keepUnknownSymbolInSymbolMappingFile = true; + + [Tooltip("symbol mapping file path")] + public string symbolMappingFile = "Assets/Obfuz/SymbolObfus/symbol-mapping.xml"; + + [Tooltip("debug symbol mapping file path, used for debugging purposes")] + public string debugSymbolMappingFile = "Assets/Obfuz/SymbolObfus/symbol-mapping-debug.xml"; + + [Tooltip("rule files")] + public string[] ruleFiles; + + [Tooltip("custom rename policy types")] + public string[] customRenamePolicyTypes; + + public string GetSymbolMappingFile() + { + return debug ? debugSymbolMappingFile : symbolMappingFile; + } + + public SymbolObfuscationSettingsFacade ToFacade() + { + return new SymbolObfuscationSettingsFacade + { + debug = debug, + obfuscatedNamePrefix = obfuscatedNamePrefix, + useConsistentNamespaceObfuscation = useConsistentNamespaceObfuscation, + detectReflectionCompatibility = detectReflectionCompatibility, + keepUnknownSymbolInSymbolMappingFile = keepUnknownSymbolInSymbolMappingFile, + symbolMappingFile = GetSymbolMappingFile(), + ruleFiles = ruleFiles?.ToList() ?? new List(), + customRenamePolicyTypes = customRenamePolicyTypes?.Select(typeName => ReflectionUtil.FindUniqueTypeInCurrentAppDomain(typeName)).ToList() ?? new List(), + }; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta new file mode 100644 index 00000000..58779d69 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Settings/SymbolObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2484a8a12a689df46b5eb7fc4ccac81f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity.meta new file mode 100644 index 00000000..8456b1dc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc9b206fbf6a69f4c99a6ec9b0b27c69 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs new file mode 100644 index 00000000..23f27653 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs @@ -0,0 +1,149 @@ +using Obfuz.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.UnityLinker; +using UnityEngine; +using FileUtil = Obfuz.Utils.FileUtil; + +namespace Obfuz.Unity +{ + public class LinkXmlProcess : IUnityLinkerProcessor + { + public int callbackOrder => ObfuzSettings.Instance.buildPipelineSettings.linkXmlProcessCallbackOrder; + + public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data) + { + return GenerateAdditionalLinkXmlFile(data.target); + } + +#if !UNITY_2021_2_OR_NEWER + + public void OnBeforeRun(BuildReport report, UnityLinkerBuildPipelineData data) + { + + } + + public void OnAfterRun(BuildReport report, UnityLinkerBuildPipelineData data) + { + + } +#endif + + public static string GenerateAdditionalLinkXmlFile(BuildTarget target) + { + ObfuzSettings settings = ObfuzSettings.Instance; + string symbolMappingFile = settings.symbolObfusSettings.GetSymbolMappingFile(); + if (!File.Exists(symbolMappingFile)) + { + Debug.LogWarning($"Symbol mapping file not found: {symbolMappingFile}. Skipping link.xml generation."); + return null; + } + string linkXmlPath = settings.GetObfuscatedLinkXmlPath(target); + FileUtil.CreateParentDir(linkXmlPath); + + var writer = System.Xml.XmlWriter.Create(linkXmlPath, + new System.Xml.XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true }); + try + { + var symbolMapping = new LiteSymbolMappingReader(symbolMappingFile); + string[] linkGuids = AssetDatabase.FindAssets("t:TextAsset"); + var linkXmlPaths = linkGuids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)) + .Where(f => Path.GetFileName(f) == "link.xml") + .ToArray(); + + var assembliesToObfuscated = new HashSet(settings.assemblySettings.GetAssembliesToObfuscate()); + + writer.WriteStartDocument(); + writer.WriteStartElement("linker"); + + // Preserve Obfuz.Runtime assembly + writer.WriteStartElement("assembly"); + writer.WriteAttributeString("fullname", "Obfuz.Runtime"); + writer.WriteAttributeString("preserve", "all"); + writer.WriteEndElement(); + + foreach (string linkPath in linkXmlPaths) + { + TransformLinkXml(linkPath, symbolMapping, assembliesToObfuscated, writer); + } + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + finally + { + writer.Close(); + } + Debug.Log($"LinkXmlProcess write {Path.GetFullPath(linkXmlPath)}"); + return Path.GetFullPath(linkXmlPath); + } + + private static void TransformLinkXml(string xmlFile, LiteSymbolMappingReader symbolMapping, HashSet assembliesToObfuscated, XmlWriter writer) + { + Debug.Log($"LinkXmlProcess transform link.xml:{xmlFile}"); + var doc = new XmlDocument(); + doc.Load(xmlFile); + var root = doc.DocumentElement; + foreach (XmlNode assNode in root.ChildNodes) + { + if (!(assNode is XmlElement assElement)) + { + continue; + } + if (assElement.Name == "assembly") + { + string assemblyName = assElement.GetAttribute("fullname"); + if (string.IsNullOrEmpty(assemblyName)) + { + throw new Exception($"Invalid node name: {assElement.Name}. attribute 'fullname' missing."); + } + if (!assembliesToObfuscated.Contains(assemblyName)) + { + continue; // Skip assemblies that are not to be obfuscated + } + writer.WriteStartElement("assembly"); + writer.WriteAttributeString("fullname", assemblyName); + if (assElement.HasAttribute("preserve")) + { + writer.WriteAttributeString("preserve", assElement.GetAttribute("preserve")); + } + + foreach (XmlNode typeNode in assElement.ChildNodes) + { + if (typeNode is XmlElement typeElement) + { + if (typeElement.Name == "type") + { + string typeName = typeElement.GetAttribute("fullname"); + if (string.IsNullOrEmpty(typeName)) + { + throw new Exception($"Invalid node name: {typeElement.Name}. attribute 'fullname' missing."); + } + if (!symbolMapping.TryGetNewTypeName(assemblyName, typeName, out string newTypeName)) + { + continue; + } + + writer.WriteStartElement("type"); + writer.WriteAttributeString("fullname", newTypeName); + if (typeElement.HasAttribute("preserve")) + { + writer.WriteAttributeString("preserve", typeElement.GetAttribute("preserve")); + } + writer.WriteEndElement(); + } + } + } + + writer.WriteEndElement(); + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs.meta new file mode 100644 index 00000000..7ddd2b48 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LinkXmlProcess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66881cca1e4a78c44b4c9fcd7f8b4fb9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs new file mode 100644 index 00000000..c04c6838 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs @@ -0,0 +1,116 @@ + +using System.Collections.Generic; +using System.Xml; + +namespace Obfuz.Unity +{ + + public class LiteSymbolMappingReader + { + class TypeMappingInfo + { + public string oldFullName; + public string newFullName; + + //public Dictionary MethodMappings = new Dictionary(); + } + + class AssemblyMappingInfo + { + public Dictionary TypeMappings = new Dictionary(); + public Dictionary MethodMappings = new Dictionary(); + } + + private readonly Dictionary _assemblies = new Dictionary(); + + public LiteSymbolMappingReader(string mappingFile) + { + LoadXmlMappingFile(mappingFile); + } + + private void LoadXmlMappingFile(string mappingFile) + { + var doc = new XmlDocument(); + doc.Load(mappingFile); + var root = doc.DocumentElement; + foreach (XmlNode node in root.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + LoadAssemblyMapping(element); + } + } + + private void LoadAssemblyMapping(XmlElement ele) + { + if (ele.Name != "assembly") + { + throw new System.Exception($"Invalid node name: {ele.Name}. Expected 'assembly'."); + } + string assName = ele.GetAttribute("name"); + if (string.IsNullOrEmpty(assName)) + { + throw new System.Exception($"Invalid node name: {ele.Name}. attribute 'name' missing."); + } + if (!_assemblies.TryGetValue(assName, out var assemblyMappingInfo)) + { + assemblyMappingInfo = new AssemblyMappingInfo(); + _assemblies[assName] = assemblyMappingInfo; + } + + foreach (XmlNode node in ele.ChildNodes) + { + if (!(node is XmlElement element)) + { + continue; + } + if (element.Name == "type") + { + LoadTypeMapping(element, assemblyMappingInfo); + } + } + } + + private void LoadTypeMapping(XmlElement ele, AssemblyMappingInfo assemblyMappingInfo) + { + string oldFullName = ele.GetAttribute("fullName"); + string newFullName = ele.GetAttribute("newFullName"); + string status = ele.GetAttribute("status"); + if (status == "Renamed") + { + if (string.IsNullOrEmpty(oldFullName) || string.IsNullOrEmpty(newFullName)) + { + throw new System.Exception($"Invalid node name: {ele.Name}. attributes 'fullName' or 'newFullName' missing."); + } + assemblyMappingInfo.TypeMappings[oldFullName] = newFullName; + } + //foreach (XmlNode node in ele.ChildNodes) + //{ + // if (!(node is XmlElement c)) + // { + // continue; + // } + // if (node.Name == "method") + // { + // LoadMethodMapping(c); + // } + //} + } + + public bool TryGetNewTypeName(string assemblyName, string oldFullName, out string newFullName) + { + newFullName = null; + if (_assemblies.TryGetValue(assemblyName, out var assemblyMappingInfo)) + { + if (assemblyMappingInfo.TypeMappings.TryGetValue(oldFullName, out newFullName)) + { + return true; + } + } + return false; + } + + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs.meta new file mode 100644 index 00000000..4abd760e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/LiteSymbolMappingReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8429e75dcc6a7e742a3cabaa3e8491c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs new file mode 100644 index 00000000..b8f2397e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs @@ -0,0 +1,8 @@ +namespace Obfuz.Unity +{ + public class ObfuscationBeginEventArgs + { + public string scriptAssembliesPath; + public string obfuscatedScriptAssembliesPath; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta new file mode 100644 index 00000000..a2d5e11a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationBeginEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e3d38018839d4844bf1e15631946e65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs new file mode 100644 index 00000000..ac2fa76a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs @@ -0,0 +1,9 @@ +namespace Obfuz.Unity +{ + public class ObfuscationEndEventArgs + { + public bool success; + public string originalScriptAssembliesPath; + public string obfuscatedScriptAssembliesPath; + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta new file mode 100644 index 00000000..fe376819 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationEndEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b198ad99d0a9c145b1bc2b29b25b8ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs new file mode 100644 index 00000000..8dd48a3d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs @@ -0,0 +1,144 @@ +using dnlib.DotNet; +using Obfuz.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using FileUtil = Obfuz.Utils.FileUtil; + +namespace Obfuz.Unity +{ + +#if UNITY_2019_1_OR_NEWER + public class ObfuscationProcess : IPostBuildPlayerScriptDLLs + { + public int callbackOrder => ObfuzSettings.Instance.buildPipelineSettings.obfuscationProcessCallbackOrder; + + public static event Action OnObfuscationBegin; + + public static event Action OnObfuscationEnd; + + public void OnPostBuildPlayerScriptDLLs(BuildReport report) + { +#if !UNITY_2022_1_OR_NEWER + RunObfuscate(report.files); +#else + RunObfuscate(report.GetFiles()); +#endif + } + + private static void BackupOriginalDlls(string srcDir, string dstDir, HashSet dllNames) + { + FileUtil.RecreateDir(dstDir); + foreach (string dllName in dllNames) + { + string srcFile = Path.Combine(srcDir, dllName + ".dll"); + string dstFile = Path.Combine(dstDir, dllName + ".dll"); + if (File.Exists(srcFile)) + { + File.Copy(srcFile, dstFile, true); + Debug.Log($"BackupOriginalDll {srcFile} -> {dstFile}"); + } + } + } + + public static void ValidateReferences(string stagingAreaTempManagedDllDir, HashSet assembliesToObfuscated, HashSet obfuscationRelativeAssemblyNames) + { + var modCtx = ModuleDef.CreateModuleContext(); + var asmResolver = (AssemblyResolver)modCtx.AssemblyResolver; + + foreach (string assFile in Directory.GetFiles(stagingAreaTempManagedDllDir, "*.dll", SearchOption.AllDirectories)) + { + ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(assFile), modCtx); + string modName = mod.Assembly.Name; + foreach (AssemblyRef assRef in mod.GetAssemblyRefs()) + { + string refAssName = assRef.Name; + if (assembliesToObfuscated.Contains(refAssName) && !obfuscationRelativeAssemblyNames.Contains(modName)) + { + throw new BuildFailedException($"assembly:{modName} references to obfuscated assembly:{refAssName}, but it's not been added to ObfuzSettings.AssemblySettings.NonObfuscatedButReferencingObfuscatedAssemblies."); + } + } + mod.Dispose(); + } + } + + private static void RunObfuscate(BuildFile[] files) + { + ObfuzSettings settings = ObfuzSettings.Instance; + if (!settings.buildPipelineSettings.enable) + { + Debug.Log("Obfuscation is disabled."); + return; + } + + Debug.Log("Obfuscation begin..."); + var buildTarget = EditorUserBuildSettings.activeBuildTarget; + + var obfuscationRelativeAssemblyNames = new HashSet(settings.assemblySettings.GetObfuscationRelativeAssemblyNames()); + string stagingAreaTempManagedDllDir = Path.GetDirectoryName(files.First(file => file.path.EndsWith(".dll")).path); + string backupPlayerScriptAssembliesPath = settings.GetOriginalAssemblyBackupDir(buildTarget); + BackupOriginalDlls(stagingAreaTempManagedDllDir, backupPlayerScriptAssembliesPath, obfuscationRelativeAssemblyNames); + + string applicationContentsPath = EditorApplication.applicationContentsPath; + + var obfuscatorBuilder = ObfuscatorBuilder.FromObfuzSettings(settings, buildTarget, false); + + var assemblySearchDirs = new List + { + stagingAreaTempManagedDllDir, + }; + obfuscatorBuilder.InsertTopPriorityAssemblySearchPaths(assemblySearchDirs); + + ValidateReferences(stagingAreaTempManagedDllDir, new HashSet(obfuscatorBuilder.CoreSettingsFacade.assembliesToObfuscate), obfuscationRelativeAssemblyNames); + + + OnObfuscationBegin?.Invoke(new ObfuscationBeginEventArgs + { + scriptAssembliesPath = stagingAreaTempManagedDllDir, + obfuscatedScriptAssembliesPath = obfuscatorBuilder.CoreSettingsFacade.obfuscatedAssemblyOutputPath, + }); + bool succ = false; + + try + { + Obfuscator obfuz = obfuscatorBuilder.Build(); + obfuz.Run(); + + foreach (var dllName in obfuscationRelativeAssemblyNames) + { + string src = $"{obfuscatorBuilder.CoreSettingsFacade.obfuscatedAssemblyOutputPath}/{dllName}.dll"; + string dst = $"{stagingAreaTempManagedDllDir}/{dllName}.dll"; + + if (!File.Exists(src)) + { + Debug.LogWarning($"obfuscation assembly not found! skip copy. path:{src}"); + continue; + } + File.Copy(src, dst, true); + Debug.Log($"obfuscate dll:{dst}"); + } + succ = true; + } + catch (Exception e) + { + succ = false; + Debug.LogException(e); + Debug.LogError($"Obfuscation failed."); + } + OnObfuscationEnd?.Invoke(new ObfuscationEndEventArgs + { + success = succ, + originalScriptAssembliesPath = backupPlayerScriptAssembliesPath, + obfuscatedScriptAssembliesPath = stagingAreaTempManagedDllDir, + }); + + Debug.Log("Obfuscation end."); + } + } +#endif +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta new file mode 100644 index 00000000..de6b391f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuscationProcess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc7a8b1e20c66164699de44d0a302cb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs new file mode 100644 index 00000000..be1147d1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs @@ -0,0 +1,88 @@ +using Obfuz.EncryptionVM; +using Obfuz.GarbageCodeGeneration; +using Obfuz.Settings; +using Obfuz.Utils; +using System.IO; +using UnityEditor; +using UnityEngine; +using FileUtil = Obfuz.Utils.FileUtil; + +namespace Obfuz.Unity +{ + public static class ObfuzMenu + { + + [MenuItem("Obfuz/Settings...", priority = 1)] + public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/Obfuz"); + + [MenuItem("Obfuz/GenerateEncryptionVM", priority = 62)] + public static void GenerateEncryptionVM() + { + EncryptionVMSettings settings = ObfuzSettings.Instance.encryptionVMSettings; + var generator = new VirtualMachineCodeGenerator(settings.codeGenerationSecretKey, settings.encryptionOpCodeCount); + generator.Generate(settings.codeOutputPath); + AssetDatabase.Refresh(); + } + + [MenuItem("Obfuz/GenerateSecretKeyFile", priority = 63)] + public static void SaveSecretFile() + { + SecretSettings settings = ObfuzSettings.Instance.secretSettings; + + var staticSecretBytes = KeyGenerator.GenerateKey(settings.defaultStaticSecretKey, VirtualMachine.SecretKeyLength); + SaveKey(staticSecretBytes, settings.staticSecretKeyOutputPath); + Debug.Log($"Save static secret key to {settings.staticSecretKeyOutputPath}"); + var dynamicSecretBytes = KeyGenerator.GenerateKey(settings.defaultDynamicSecretKey, VirtualMachine.SecretKeyLength); + SaveKey(dynamicSecretBytes, settings.dynamicSecretKeyOutputPath); + Debug.Log($"Save dynamic secret key to {settings.dynamicSecretKeyOutputPath}"); + AssetDatabase.Refresh(); + } + + [MenuItem("Obfuz/GarbageCode/GenerateCodes", priority = 100)] + public static void GenerateGarbageCodes() + { + Debug.Log($"Generating garbage codes begin."); + GarbageCodeGenerationSettings settings = ObfuzSettings.Instance.garbageCodeGenerationSettings; + var generator = new GarbageCodeGenerator(settings); + generator.Generate(); + AssetDatabase.Refresh(); + Debug.Log($"Generating garbage codes end."); + } + + [MenuItem("Obfuz/GarbageCode/CleanGeneratedCodes", priority = 101)] + public static void CleanGeneratedGarbageCodes() + { + Debug.Log($"Clean generated garbage codes begin."); + GarbageCodeGenerationSettings settings = ObfuzSettings.Instance.garbageCodeGenerationSettings; + var generator = new GarbageCodeGenerator(settings); + generator.CleanCodes(); + AssetDatabase.Refresh(); + Debug.Log($"Clean generated garbage codes end."); + } + + private static void SaveKey(byte[] secret, string secretOutputPath) + { + FileUtil.CreateParentDir(secretOutputPath); + File.WriteAllBytes(secretOutputPath, secret); + } + + [MenuItem("Obfuz/Documents/Quick Start")] + public static void OpenQuickStart() => Application.OpenURL("https://www.obfuz.com/docs/beginner/quickstart"); + + [MenuItem("Obfuz/Documents/FAQ")] + public static void OpenFAQ() => Application.OpenURL("https://www.obfuz.com/docs/help/faq"); + + [MenuItem("Obfuz/Documents/Common Errors")] + public static void OpenCommonErrors() => Application.OpenURL("https://www.obfuz.com/docs/help/commonerrors"); + + [MenuItem("Obfuz/Documents/Bug Report")] + public static void OpenBugReport() => Application.OpenURL("https://www.obfuz.com/docs/help/issue"); + + [MenuItem("Obfuz/Documents/GitHub")] + public static void OpenGitHub() => Application.OpenURL("https://github.com/focus-creative-games/obfuz"); + + [MenuItem("Obfuz/Documents/About")] + public static void OpenAbout() => Application.OpenURL("https://www.obfuz.com/docs/intro"); + } + +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta new file mode 100644 index 00000000..ae1f6f80 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/ObfuzMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce8d804a6c4640e45a3d5c98b30c0c31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs new file mode 100644 index 00000000..9d838aee --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs @@ -0,0 +1,57 @@ +using Obfuz.Utils; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Obfuz.Unity +{ + public class UnityProjectManagedAssemblyResolver : AssemblyResolverBase + { + private readonly Dictionary _managedAssemblyNameToPaths = new Dictionary(); + + public UnityProjectManagedAssemblyResolver(BuildTarget target) + { + string[] dllGuids = AssetDatabase.FindAssets("t:DefaultAsset"); + var dllPaths = dllGuids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)) + .Where(f => f.EndsWith(".dll")) + .Where(dllPath => + { + PluginImporter importer = AssetImporter.GetAtPath(dllPath) as PluginImporter; + if (importer == null || importer.isNativePlugin) + { + return false; + } + if (!importer.GetCompatibleWithAnyPlatform() && !importer.GetCompatibleWithPlatform(target)) + { + return false; + } + return true; + }).ToArray(); + + foreach (string dllPath in dllPaths) + { + Debug.Log($"UnityProjectManagedAssemblyResolver find managed dll:{dllPath}"); + string assName = Path.GetFileNameWithoutExtension(dllPath); + if (_managedAssemblyNameToPaths.TryGetValue(assName, out var existAssPath)) + { + Debug.LogWarning($"UnityProjectManagedAssemblyResolver find duplicate assembly1:{existAssPath} assembly2:{dllPath}"); + } + else + { + _managedAssemblyNameToPaths.Add(Path.GetFileNameWithoutExtension(dllPath), dllPath); + } + } + } + + public override string ResolveAssembly(string assemblyName) + { + if (_managedAssemblyNameToPaths.TryGetValue(assemblyName, out string assemblyPath)) + { + return assemblyPath; + } + return null; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs.meta new file mode 100644 index 00000000..ac1c4aa1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Unity/UnityProjectManagedAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 20abbffaf6419c448883ffaa21949753 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils.meta new file mode 100644 index 00000000..8a3e253d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ade28aaad1116b143a4027071e71010f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs new file mode 100644 index 00000000..0c3eeaa4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs @@ -0,0 +1,88 @@ +using dnlib.DotNet; +using System.Collections.Generic; +using System.IO; + +namespace Obfuz.Utils +{ + + public class AssemblyCache + { + private readonly IAssemblyResolver _assemblyPathResolver; + private readonly ModuleContext _modCtx; + private readonly AssemblyResolver _asmResolver; + private bool _enableTypeDefCache; + + + public ModuleContext ModCtx => _modCtx; + + public Dictionary LoadedModules { get; } = new Dictionary(); + + public AssemblyCache(IAssemblyResolver assemblyResolver) + { + _enableTypeDefCache = true; + _assemblyPathResolver = assemblyResolver; + _modCtx = ModuleDef.CreateModuleContext(); + _asmResolver = (AssemblyResolver)_modCtx.AssemblyResolver; + _asmResolver.EnableTypeDefCache = _enableTypeDefCache; + _asmResolver.UseGAC = false; + } + + public bool EnableTypeDefCache + { + get => _enableTypeDefCache; + set + { + _enableTypeDefCache = value; + _asmResolver.EnableTypeDefCache = value; + foreach (var mod in LoadedModules.Values) + { + mod.EnableTypeDefFindCache = value; + } + } + } + + + public ModuleDefMD TryLoadModule(string moduleName) + { + string dllPath = _assemblyPathResolver.ResolveAssembly(moduleName); + if (string.IsNullOrEmpty(dllPath)) + { + return null; + } + return LoadModule(moduleName); + } + + public ModuleDefMD LoadModule(string moduleName) + { + // Debug.Log($"load module:{moduleName}"); + if (LoadedModules.TryGetValue(moduleName, out var mod)) + { + return mod; + } + string assemblyPath = _assemblyPathResolver.ResolveAssembly(moduleName); + if (string.IsNullOrEmpty(assemblyPath)) + { + throw new FileNotFoundException($"Assembly {moduleName} not found"); + } + mod = DoLoadModule(assemblyPath); + LoadedModules.Add(moduleName, mod); + + + foreach (var refAsm in mod.GetAssemblyRefs()) + { + LoadModule(refAsm.Name); + } + + return mod; + } + + private ModuleDefMD DoLoadModule(string dllPath) + { + //Debug.Log($"do load module:{dllPath}"); + ModuleDefMD mod = ModuleDefMD.Load(File.ReadAllBytes(dllPath), _modCtx); + mod.EnableTypeDefFindCache = _enableTypeDefCache; + _asmResolver.AddToCache(mod); + return mod; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta new file mode 100644 index 00000000..bb922298 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce64ad992f9807d4994d4f41a54b170b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs new file mode 100644 index 00000000..73aa7e6a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs @@ -0,0 +1,7 @@ +namespace Obfuz.Utils +{ + public abstract class AssemblyResolverBase : IAssemblyResolver + { + public abstract string ResolveAssembly(string assemblyName); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta new file mode 100644 index 00000000..4a7f4a0e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssemblyResolverBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7332848ca18498459e6248d06bc5b31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs new file mode 100644 index 00000000..112899a7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs @@ -0,0 +1,28 @@ +using UnityEngine.Assertions; + +namespace Obfuz.Utils +{ + public static class AssertUtil + { + private static bool IsArrayEqual(byte[] a, byte[] b) + { + if (a.Length != b.Length) + { + return false; + } + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; + } + + public static void AreArrayEqual(byte[] expected, byte[] actual, string message) + { + Assert.IsTrue(IsArrayEqual(expected, actual), message); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs.meta new file mode 100644 index 00000000..e0097382 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/AssertUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aba0c82c527b6494ab5c9c67c25656ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs new file mode 100644 index 00000000..f2f22884 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs @@ -0,0 +1,90 @@ +using dnlib.DotNet; +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + public class BurstCompileComputeCache + { + private readonly List _modulesToObfuscate; + private readonly List _allObfuscationRelativeModules; + + private readonly HashSet _burstCompileMethods = new HashSet(); + private readonly HashSet _burstCompileRelativeMethods = new HashSet(); + public BurstCompileComputeCache(List modulesToObfuscate, List allObfuscationRelativeModules) + { + _modulesToObfuscate = modulesToObfuscate; + _allObfuscationRelativeModules = allObfuscationRelativeModules; + Build(); + } + + + private void BuildBurstCompileMethods() + { + foreach (var module in _allObfuscationRelativeModules) + { + foreach (var type in module.GetTypes()) + { + bool hasBurstCompileAttribute = MetaUtil.HasBurstCompileAttribute(type); + foreach (var method in type.Methods) + { + if (hasBurstCompileAttribute || MetaUtil.HasBurstCompileAttribute(method)) + { + _burstCompileMethods.Add(method); + } + } + } + } + } + + private void CollectBurstCompileReferencedMethods() + { + var modulesToObfuscateSet = new HashSet(_modulesToObfuscate); + var allObfuscationRelativeModulesSet = new HashSet(_allObfuscationRelativeModules); + + var pendingWalking = new Queue(_burstCompileMethods); + var visitedMethods = new HashSet(); + while (pendingWalking.Count > 0) + { + var method = pendingWalking.Dequeue(); + + if (!visitedMethods.Add(method)) + { + continue; // Skip already visited methods + } + if (modulesToObfuscateSet.Contains(method.Module)) + { + _burstCompileRelativeMethods.Add(method); + } + if (!method.HasBody) + { + continue; + } + // Check for calls to other methods + foreach (var instruction in method.Body.Instructions) + { + if (instruction.OpCode.Code == dnlib.DotNet.Emit.Code.Call || + instruction.OpCode.Code == dnlib.DotNet.Emit.Code.Callvirt) + { + MethodDef calledMethod = ((IMethod)instruction.Operand).ResolveMethodDef(); + if (calledMethod == null || !allObfuscationRelativeModulesSet.Contains(calledMethod.Module) || visitedMethods.Contains(calledMethod)) + { + continue; // Skip if the method could not be resolved + } + pendingWalking.Enqueue(calledMethod); + } + } + } + } + + private void Build() + { + BuildBurstCompileMethods(); + CollectBurstCompileReferencedMethods(); + } + + public bool IsBurstCompileMethodOrReferencedByBurstCompileMethod(MethodDef method) + { + return _burstCompileRelativeMethods.Contains(method); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs.meta new file mode 100644 index 00000000..07dd6517 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/BurstCompileComputeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37efeee0ad0b5c34e84bd1b7b401672a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs new file mode 100644 index 00000000..1e0f1d02 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + public class CachedDictionary + { + private readonly Func _valueFactory; + private readonly Dictionary _cache; + + public CachedDictionary(Func valueFactory) + { + _cache = new Dictionary(); + _valueFactory = valueFactory; + } + + public CachedDictionary(IEqualityComparer equalityComparer, Func valueFactory) + { + _cache = new Dictionary(equalityComparer); + _valueFactory = valueFactory; + } + + public V GetValue(K key) + { + if (!_cache.TryGetValue(key, out var value)) + { + value = _valueFactory(key); + _cache[key] = value; + } + return value; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs.meta new file mode 100644 index 00000000..115dde01 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CachedDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66494da674feeb741b889590cb663d4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs new file mode 100644 index 00000000..6753fe5d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + public static class CollectionExtensions + { + public static void AddRange(this HashSet values, IEnumerable newValues) + { + foreach (var value in newValues) + { + values.Add(value); + } + } + + public static V GetValueOrDefault(this Dictionary dic, K key) + { + return dic.TryGetValue(key, out V v) ? v : default(V); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta new file mode 100644 index 00000000..ba4aa2ac --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CollectionExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58fc4438f86bc174aba662f1d7058f45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs new file mode 100644 index 00000000..fd1c9443 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.Utils +{ + public class CombinedAssemblyResolver : AssemblyResolverBase + { + private readonly List _resolvers; + + public CombinedAssemblyResolver(params IAssemblyResolver[] resolvers) + { + _resolvers = resolvers.ToList(); + } + + public override string ResolveAssembly(string assemblyName) + { + foreach (var resolver in _resolvers) + { + var assemblyPath = resolver.ResolveAssembly(assemblyName); + if (assemblyPath != null) + { + return assemblyPath; + } + } + return null; + } + + public void InsertFirst(IAssemblyResolver resolver) + { + _resolvers.Insert(0, resolver); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs.meta new file mode 100644 index 00000000..dd9890d7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/CombinedAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1576f3534f31509458104d2a7ebcd9cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs new file mode 100644 index 00000000..bc2c5492 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs @@ -0,0 +1,78 @@ +using Obfuz.Settings; +using System; + +namespace Obfuz.Utils +{ + public static class ConfigUtil + { + + public static bool ParseBool(string str) + { + switch (str.ToLowerInvariant()) + { + case "1": + case "true": return true; + case "0": + case "false": return false; + default: throw new Exception($"Invalid bool value {str}"); + } + } + + public static bool? ParseNullableBool(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + switch (str.ToLowerInvariant()) + { + case "1": + case "true": return true; + case "0": + case "false": return false; + default: throw new Exception($"Invalid bool value {str}"); + } + } + + public static int? ParseNullableInt(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return int.Parse(str); + } + + public static long? ParseNullableLong(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return long.Parse(str); + } + + public static float? ParseNullableFloat(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return float.Parse(str); + } + + public static double? ParseNullableDouble(string str) + { + if (string.IsNullOrEmpty(str)) + { + return null; + } + return double.Parse(str); + } + + public static ObfuscationLevel ParseObfuscationLevel(string str) + { + return (ObfuscationLevel)Enum.Parse(typeof(ObfuscationLevel), str); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta new file mode 100644 index 00000000..bba160a6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConfigUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff35a8e07f37adf4483eaf5cc5da5c78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs new file mode 100644 index 00000000..67c64d05 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs @@ -0,0 +1,177 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Data; +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + internal static class ConstObfusUtil + { + public static void LoadConstInt(int a, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + Instruction inst; + if (random.NextInPercentage(constProbability)) + { + inst = Instruction.Create(OpCodes.Ldc_I4, a); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + inst = Instruction.Create(OpCodes.Ldsfld, field); + } + outputInsts.Add(inst); + } + + public static void LoadConstLong(long a, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + Instruction inst; + if (random.NextInPercentage(constProbability)) + { + inst = Instruction.Create(OpCodes.Ldc_I8, a); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + inst = Instruction.Create(OpCodes.Ldsfld, field); + } + outputInsts.Add(inst); + } + + public static void LoadConstFloat(float a, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + Instruction inst; + if (random.NextInPercentage(constProbability)) + { + inst = Instruction.Create(OpCodes.Ldc_R4, a); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + inst = Instruction.Create(OpCodes.Ldsfld, field); + } + outputInsts.Add(inst); + } + + public static void LoadConstDouble(double a, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + Instruction inst; + if (random.NextInPercentage(constProbability)) + { + inst = Instruction.Create(OpCodes.Ldc_R8, a); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + inst = Instruction.Create(OpCodes.Ldsfld, field); + } + outputInsts.Add(inst); + } + + + public static void LoadConstTwoInt(int a, int b, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + if (random.NextInPercentage(constProbability)) + { + outputInsts.Add(Instruction.Create(OpCodes.Ldc_I4, a)); + + // at most one ldc instruction + FieldDef field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + + if (random.NextInPercentage(constProbability)) + { + // at most one ldc instruction + outputInsts.Add(Instruction.Create(OpCodes.Ldc_I4, b)); + } + else + { + field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + } + } + + public static void LoadConstTwoLong(long a, long b, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + if (random.NextInPercentage(constProbability)) + { + outputInsts.Add(Instruction.Create(OpCodes.Ldc_I8, a)); + // at most one ldc instruction + FieldDef field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + if (random.NextInPercentage(constProbability)) + { + // at most one ldc instruction + outputInsts.Add(Instruction.Create(OpCodes.Ldc_I8, b)); + } + else + { + field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + } + } + + public static void LoadConstTwoFloat(float a, float b, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + if (random.NextInPercentage(constProbability)) + { + outputInsts.Add(Instruction.Create(OpCodes.Ldc_R4, a)); + // at most one ldc instruction + FieldDef field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + if (random.NextInPercentage(constProbability)) + { + // at most one ldc instruction + outputInsts.Add(Instruction.Create(OpCodes.Ldc_R4, b)); + } + else + { + field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + } + } + + public static void LoadConstTwoDouble(double a, double b, IRandom random, float constProbability, ConstFieldAllocator constFieldAllocator, List outputInsts) + { + if (random.NextInPercentage(constProbability)) + { + outputInsts.Add(Instruction.Create(OpCodes.Ldc_R8, a)); + // at most one ldc instruction + FieldDef field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + else + { + FieldDef field = constFieldAllocator.Allocate(a); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + if (random.NextInPercentage(constProbability)) + { + // at most one ldc instruction + outputInsts.Add(Instruction.Create(OpCodes.Ldc_R8, b)); + } + else + { + field = constFieldAllocator.Allocate(b); + outputInsts.Add(Instruction.Create(OpCodes.Ldsfld, field)); + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs.meta new file mode 100644 index 00000000..1646815d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ConstObfusUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4330e358b953be54c9f1ada2ff34156d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs new file mode 100644 index 00000000..30ccef2d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs @@ -0,0 +1,21 @@ +using dnlib.DotNet; +using System; + +namespace Obfuz.Utils +{ + public class DisableTypeDefFindCacheScope : IDisposable + { + private readonly ModuleDef _module; + + public DisableTypeDefFindCacheScope(ModuleDef module) + { + _module = module; + _module.EnableTypeDefFindCache = false; + } + + public void Dispose() + { + _module.EnableTypeDefFindCache = true; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs.meta new file mode 100644 index 00000000..349b119e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/DisableTypeDefFindCacheScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89640cb831c78f8429c861fb49bae0e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs new file mode 100644 index 00000000..d2f956ff --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs @@ -0,0 +1,45 @@ +using System; +using UnityEngine; + +namespace Obfuz.Utils +{ + public static class EncryptionUtil + { + public static int GetBitCount(int value) + { + int count = 0; + while (value > 0) + { + count++; + value >>= 1; + } + return count; + } + + public static int GenerateEncryptionOpCodes(IRandom random, IEncryptor encryptor, int encryptionLevel) + { + if (encryptionLevel <= 0 || encryptionLevel > 4) + { + throw new ArgumentException($"Invalid encryption level: {encryptionLevel}, should be in range [1,4]"); + } + int vmOpCodeCount = encryptor.OpCodeCount; + long ops = 0; + for (int i = 0; i < encryptionLevel; i++) + { + long newOps = ops * vmOpCodeCount; + // don't use 0 + int op = random.NextInt(1, vmOpCodeCount); + newOps |= (uint)op; + if (newOps > uint.MaxValue) + { + Debug.LogWarning($"OpCode overflow. encryptionLevel:{encryptionLevel}, vmOpCodeCount:{vmOpCodeCount}"); + } + else + { + ops = newOps; + } + } + return (int)ops; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta new file mode 100644 index 00000000..5c82b2ba --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/EncryptionUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b8fc4c92fa6f0b40a9734b347cd265c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs new file mode 100644 index 00000000..f7c38ee4 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using System.Threading; + +namespace Obfuz.Utils +{ + public static class FileUtil + { + public static void CreateParentDir(string path) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + public static void RemoveDir(string dir, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"removeDir dir:{dir}"); + } + + int maxTryCount = 5; + for (int i = 0; i < maxTryCount; ++i) + { + try + { + if (!Directory.Exists(dir)) + { + return; + } + foreach (var file in Directory.GetFiles(dir)) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + foreach (var subDir in Directory.GetDirectories(dir)) + { + RemoveDir(subDir); + } + Directory.Delete(dir, true); + break; + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"removeDir:{dir} with exception:{e}. try count:{i}"); + Thread.Sleep(100); + } + } + } + + public static void RecreateDir(string dir) + { + if (Directory.Exists(dir)) + { + RemoveDir(dir, true); + } + Directory.CreateDirectory(dir); + } + + private static void CopyWithCheckLongFile(string srcFile, string dstFile) + { + var maxPathLength = 255; +#if UNITY_EDITOR_OSX + maxPathLength = 1024; +#endif + if (srcFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"srcFile:{srcFile} path is too long. skip copy!"); + return; + } + if (dstFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"dstFile:{dstFile} path is too long. skip copy!"); + return; + } + File.Copy(srcFile, dstFile); + } + + public static void CopyDir(string src, string dst, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"copyDir {src} => {dst}"); + } + RemoveDir(dst); + Directory.CreateDirectory(dst); + foreach (var file in Directory.GetFiles(src)) + { + CopyWithCheckLongFile(file, $"{dst}/{Path.GetFileName(file)}"); + } + foreach (var subDir in Directory.GetDirectories(src)) + { + CopyDir(subDir, $"{dst}/{Path.GetFileName(subDir)}"); + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta new file mode 100644 index 00000000..c8523ce5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/FileUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6385af8bd061b142a3d7dcf41ab7e79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs new file mode 100644 index 00000000..52328adb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs @@ -0,0 +1,111 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.Utils +{ + public sealed class GenericArgumentContext + { + public readonly List typeArgsStack; + public readonly List methodArgsStack; + + public GenericArgumentContext(IList typeArgsStack, IList methodArgsStack) + { + this.typeArgsStack = typeArgsStack?.ToList(); + this.methodArgsStack = methodArgsStack?.ToList(); + } + + public TypeSig Resolve(TypeSig typeSig) + { + if (!typeSig.ContainsGenericParameter) + { + return typeSig; + } + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.ByRef: return new ByRefSig(Resolve(typeSig.Next)); + + case ElementType.SZArray: return new SZArraySig(Resolve(typeSig.Next)); + case ElementType.Array: + { + var ara = (ArraySig)typeSig; + return new ArraySig(Resolve(typeSig.Next), ara.Rank, ara.Sizes, ara.LowerBounds); + } + + case ElementType.Var: + { + GenericVar genericVar = (GenericVar)typeSig; + var newSig = Resolve(typeArgsStack, genericVar.Number); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + + case ElementType.MVar: + { + GenericMVar genericVar = (GenericMVar)typeSig; + var newSig = Resolve(methodArgsStack, genericVar.Number); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + case ElementType.GenericInst: + { + var gia = (GenericInstSig)typeSig; + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => Resolve(ga)).ToList()); + } + + case ElementType.FnPtr: + { + var fptr = (FnPtrSig)typeSig; + var cs = fptr.Signature; + CallingConventionSig ccs; + switch (cs) + { + case MethodSig ms: + { + ccs = new MethodSig(ms.GetCallingConvention(), ms.GenParamCount, Resolve(ms.RetType), ms.Params.Select(p => Resolve(p)).ToList()); + break; + } + case PropertySig ps: + { + ccs = new PropertySig(ps.HasThis, Resolve(ps.RetType)); + break; + } + case GenericInstMethodSig gims: + { + ccs = new GenericInstMethodSig(gims.GenericArguments.Select(ga => Resolve(ga)).ToArray()); + break; + } + default: throw new NotSupportedException(cs.ToString()); + } + return new FnPtrSig(ccs); + } + + case ElementType.ValueArray: + { + var vas = (ValueArraySig)typeSig; + return new ValueArraySig(Resolve(vas.Next), vas.Size); + } + default: return typeSig; + } + } + + private TypeSig Resolve(List args, uint number) + { + if (args == null) + { + throw new ArgumentNullException(nameof(args)); + } + return args[(int)number]; + } + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta new file mode 100644 index 00000000..fd13834f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/GenericArgumentContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdefb4e144f6a98418c7bd02eab51039 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs new file mode 100644 index 00000000..3e128292 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs @@ -0,0 +1,67 @@ +using dnlib.DotNet; +using System.Collections; +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + public static class HashUtil + { + public static int CombineHash(int hash1, int hash2) + { + return hash1 * 1566083941 + hash2; + } + + public static int ComputeHash(TypeSig sig) + { + return TypeEqualityComparer.Instance.GetHashCode(sig); + } + + public static int ComputeHash(IList sigs) + { + int hash = 135781321; + TypeEqualityComparer tc = TypeEqualityComparer.Instance; + foreach (var sig in sigs) + { + hash = hash * 1566083941 + tc.GetHashCode(sig); + } + return hash; + } + + public static unsafe int ComputeHash(string s) + { + fixed (char* ptr = s) + { + int num = 352654597; + int num2 = num; + int* ptr2 = (int*)ptr; + int num3; + for (num3 = s.Length; num3 > 2; num3 -= 4) + { + num = ((num << 5) + num + (num >> 27)) ^ *ptr2; + num2 = ((num2 << 5) + num2 + (num2 >> 27)) ^ ptr2[1]; + ptr2 += 2; + } + + if (num3 > 0) + { + num = ((num << 5) + num + (num >> 27)) ^ *ptr2; + } + + return num + num2 * 1566083941; + } + } + + public static int ComputePrimitiveOrStringOrBytesHashCode(object obj) + { + if (obj is byte[] bytes) + { + return StructuralComparisons.StructuralEqualityComparer.GetHashCode(bytes); + } + if (obj is string s) + { + return HashUtil.ComputeHash(s); + } + return obj.GetHashCode(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta new file mode 100644 index 00000000..d89f8f0d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/HashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b4cd05dd413bfa4ebb9fcbe591b1486 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs new file mode 100644 index 00000000..9e048793 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs @@ -0,0 +1,7 @@ +namespace Obfuz.Utils +{ + public interface IAssemblyResolver + { + string ResolveAssembly(string assemblyName); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta new file mode 100644 index 00000000..917a107e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0254d9726a78e146af99a61641b47d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs new file mode 100644 index 00000000..e37ca145 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs @@ -0,0 +1,17 @@ +namespace Obfuz.Utils +{ + public interface IRandom + { + int NextInt(int min, int max); + + int NextInt(int max); + + int NextInt(); + + long NextLong(); + + float NextFloat(); + + bool NextInPercentage(float percentage); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta new file mode 100644 index 00000000..086b0737 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/IRandom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 959c821ff51056c4ebca3f89aeeff03d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs new file mode 100644 index 00000000..ea5a6816 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs @@ -0,0 +1,37 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Obfuz.Utils +{ + public static class KeyGenerator + { + public static byte[] GenerateKey(string initialString, int keyLength) + { + byte[] initialBytes = Encoding.UTF8.GetBytes(initialString); + using (var sha512 = SHA512.Create()) + { + byte[] hash = sha512.ComputeHash(initialBytes); + byte[] key = new byte[keyLength]; + int bytesCopied = 0; + while (bytesCopied < key.Length) + { + if (bytesCopied > 0) + { + // 再次哈希之前的哈希值以生成更多数据 + hash = sha512.ComputeHash(hash); + } + int bytesToCopy = Math.Min(hash.Length, key.Length - bytesCopied); + Buffer.BlockCopy(hash, 0, key, bytesCopied, bytesToCopy); + bytesCopied += bytesToCopy; + } + return key; + } + } + + public static int[] ConvertToIntKey(byte[] key) + { + return EncryptorBase.ConvertToIntKey(key); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta new file mode 100644 index 00000000..7d11ff06 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/KeyGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5441ac16fd88a8848af862be23bd2ecb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs new file mode 100644 index 00000000..dad6d2ef --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs @@ -0,0 +1,58 @@ +using System; + +namespace Obfuz.Utils +{ + + internal static class MathUtil + { + //public static int ModInverseOdd32(int sa) + //{ + // uint a = (uint)sa; + // if (a % 2 == 0) + // throw new ArgumentException("Input must be an odd number.", nameof(a)); + + // uint x = 1; // 初始解:x₀ = 1 (mod 2) + // for (int i = 0; i < 5; i++) // 迭代5次(2^1 → 2^32) + // { + // int shift = 2 << i; // 当前模数为 2^(2^(i+1)) + // ulong mod = 1UL << shift; // 使用 ulong 避免溢出 + // ulong ax = (ulong)a * x; // 计算 a*x(64位避免截断) + // ulong term = (2 - ax) % mod; + // x = (uint)((x * term) % mod); // 更新 x,结果截断为 uint + // } + // return (int)x; // 最终解为 x₅ mod 2^32 + //} + + public static int ModInverse32(int sa) + { + uint x = (uint)sa; + if ((x & 1) == 0) + throw new ArgumentException("x must be odd (coprime with 2^32)"); + + uint inv = x; + inv = inv * (2 - x * inv); // 1 + inv = inv * (2 - x * inv); // 2 + inv = inv * (2 - x * inv); // 3 + inv = inv * (2 - x * inv); // 4 + inv = inv * (2 - x * inv); // 5 + return (int)inv; + } + + public static long ModInverse64(long sx) + { + ulong x = (ulong)sx; + if ((x & 1) == 0) + throw new ArgumentException("x must be odd (coprime with 2^64)"); + + ulong inv = x; + inv *= 2 - x * inv; // 1 + inv *= 2 - x * inv; // 2 + inv *= 2 - x * inv; // 3 + inv *= 2 - x * inv; // 4 + inv *= 2 - x * inv; // 5 + inv *= 2 - x * inv; // 6 + + return (long)inv; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs.meta new file mode 100644 index 00000000..4dc5598c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MathUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d5962b5e88adac40a2b1c65a8d304bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs new file mode 100644 index 00000000..ac48fcac --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs @@ -0,0 +1,922 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine.Assertions; + +namespace Obfuz.Utils +{ + + public static class MetaUtil + { + public static string GetModuleNameWithoutExt(string moduleName) + { + return Path.GetFileNameWithoutExtension(moduleName); + } + + public static (string, string) SplitNamespaceAndName(string fullName) + { + int index = fullName.LastIndexOf('/'); + if (index == -1) + { + int index2 = fullName.IndexOf('.'); + return index2 >= 0 ? (fullName.Substring(0, index2), fullName.Substring(index2 + 1)) : ("", fullName); + } + return ("", fullName.Substring(index + 1)); + } + + public static bool IsVoidType(TypeSig type) + { + return type.RemovePinnedAndModifiers().ElementType == ElementType.Void; + } + + public static TypeDef GetBaseTypeDef(TypeDef type) + { + ITypeDefOrRef baseType = type.BaseType; + if (baseType == null) + { + return null; + } + TypeDef baseTypeDef = baseType.ResolveTypeDef(); + if (baseTypeDef != null) + { + return baseTypeDef; + } + if (baseType is TypeSpec baseTypeSpec) + { + GenericInstSig genericIns = baseTypeSpec.TypeSig.ToGenericInstSig(); + return genericIns.GenericType.TypeDefOrRef.ResolveTypeDefThrow(); + } + else + { + throw new Exception($"GetBaseTypeDef: {type} fail"); + } + } + + public static TypeDef GetTypeDefOrGenericTypeBaseThrowException(ITypeDefOrRef type) + { + if (type.IsTypeDef) + { + return (TypeDef)type; + } + if (type.IsTypeRef) + { + return type.ResolveTypeDefThrow(); + } + if (type.IsTypeSpec) + { + GenericInstSig gis = type.TryGetGenericInstSig(); + return gis.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + } + throw new NotSupportedException($"{type}"); + } + + public static TypeDef GetTypeDefOrGenericTypeBaseOrNull(ITypeDefOrRef type) + { + if (type.IsTypeDef) + { + return (TypeDef)type; + } + if (type.IsTypeRef) + { + return type.ResolveTypeDefThrow(); + } + if (type.IsTypeSpec) + { + GenericInstSig gis = type.TryGetGenericInstSig(); + if (gis == null) + { + return null; + } + return gis.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + } + return null; + } + + public static TypeDef GetMemberRefTypeDefParentOrNull(IMemberRefParent parent) + { + if (parent is TypeDef typeDef) + { + return typeDef; + } + if (parent is TypeRef typeRef) + { + return typeRef.ResolveTypeDefThrow(); + } + if (parent is TypeSpec typeSpec) + { + GenericInstSig gis = typeSpec.TryGetGenericInstSig(); + if (gis == null) + { + return null; + } + return gis.GenericType.TypeDefOrRef.ResolveTypeDefThrow(); + } + return null; + } + + public static bool IsInheritFromDOTSTypes(TypeDef typeDef) + { + TypeDef cur = typeDef; + while (true) + { + if (cur.Namespace.StartsWith("Unity.Entities") || + //cur.Namespace.StartsWith("Unity.Jobs") || + cur.Namespace.StartsWith("Unity.Burst")) + { + return true; + } + foreach (var interfaceType in cur.Interfaces) + { + TypeDef interfaceTypeDef = interfaceType.Interface.ResolveTypeDef(); + if (interfaceTypeDef != null && (interfaceTypeDef.Namespace.StartsWith("Unity.Entities") || + //interfaceTypeDef.Namespace.StartsWith("Unity.Jobs") || + interfaceTypeDef.Namespace.StartsWith("Unity.Burst"))) + { + return true; + } + } + + cur = GetBaseTypeDef(cur); + if (cur == null) + { + return false; + } + } + } + + public static bool IsInheritFromMonoBehaviour(TypeDef typeDef) + { + TypeDef cur = typeDef; + while (true) + { + cur = GetBaseTypeDef(cur); + if (cur == null) + { + return false; + } + if (cur.Name == "MonoBehaviour" && cur.Namespace == "UnityEngine" && cur.Module.Name == "UnityEngine.CoreModule.dll") + { + return true; + } + } + } + + + public static bool IsScriptType(TypeDef type) + { + for (TypeDef parentType = GetBaseTypeDef(type); parentType != null; parentType = GetBaseTypeDef(parentType)) + { + if ((parentType.Name == "MonoBehaviour" || parentType.Name == "ScriptableObject") + && parentType.Namespace == "UnityEngine" + && parentType.Module.Assembly.Name == "UnityEngine.CoreModule") + { + return true; + } + } + + return false; + } + + public static bool IsSerializableType(TypeDef type) + { + return type.IsSerializable; + } + + public static bool IsScriptOrSerializableType(TypeDef type) + { + return type.IsSerializable || IsScriptType(type); + } + + public static bool IsSerializableTypeSig(TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + case ElementType.I8: + case ElementType.U8: + case ElementType.R4: + case ElementType.R8: + case ElementType.String: + return true; + case ElementType.Class: + return IsScriptOrSerializableType(typeSig.ToTypeDefOrRef().ResolveTypeDefThrow()); + case ElementType.ValueType: + { + TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.IsEnum) + { + return true; + } + return typeDef.IsSerializable; + } + case ElementType.GenericInst: + { + GenericInstSig genericIns = typeSig.ToGenericInstSig(); + TypeDef typeDef = genericIns.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow(); + return typeDef.FullName == "System.Collections.Generic.List`1" && IsSerializableTypeSig(genericIns.GenericArguments[0]); + } + case ElementType.SZArray: + { + return IsSerializableTypeSig(typeSig.RemovePinnedAndModifiers().Next); + } + default: + return false; + } + } + + public static bool IsSerializableField(FieldDef field) + { + if (field.IsStatic) + { + return false; + } + var fieldSig = field.FieldSig.Type; + if (field.IsPublic) + { + return IsSerializableTypeSig(fieldSig); + } + if (field.CustomAttributes.Any(c => c.TypeFullName == "UnityEngine.SerializeField")) + { + //UnityEngine.Debug.Assert(IsSerializableTypeSig(fieldSig)); + return true; + } + return false; + } + + public static bool MayRenameCustomDataType(ElementType type) + { + return type == ElementType.Class || type == ElementType.ValueType || type == ElementType.Object || type == ElementType.SZArray; + } + + public static TypeSig RetargetTypeRefInTypeSig(TypeSig type) + { + TypeSig next = type.Next; + TypeSig newNext = next != null ? RetargetTypeRefInTypeSig(next) : null; + if (type.IsModifier || type.IsPinned) + { + if (next == newNext) + { + return type; + } + if (type is CModReqdSig cmrs) + { + return new CModReqdSig(cmrs.Modifier, newNext); + } + if (type is CModOptSig cmos) + { + return new CModOptSig(cmos.Modifier, newNext); + } + if (type is PinnedSig ps) + { + return new PinnedSig(newNext); + } + throw new System.NotSupportedException(type.ToString()); + } + switch (type.ElementType) + { + case ElementType.Ptr: + { + if (next == newNext) + { + return type; + } + return new PtrSig(newNext); + } + case ElementType.ValueType: + case ElementType.Class: + { + var vts = type as ClassOrValueTypeSig; + if (vts.TypeDefOrRef is TypeDef typeDef) + { + return type; + } + TypeRef typeRef = (TypeRef)vts.TypeDefOrRef; + if (typeRef.DefinitionAssembly.IsCorLib()) + { + return type; + } + typeDef = typeRef.ResolveTypeDefThrow(); + return type.IsClassSig ? (TypeSig)new ClassSig(typeDef) : new ValueTypeSig(typeDef); + } + case ElementType.Array: + { + if (next == newNext) + { + return type; + } + return new ArraySig(newNext); + } + case ElementType.SZArray: + { + if (next == newNext) + { + return type; + } + return new SZArraySig(newNext); + } + case ElementType.GenericInst: + { + var gis = type as GenericInstSig; + ClassOrValueTypeSig genericType = gis.GenericType; + ClassOrValueTypeSig newGenericType = (ClassOrValueTypeSig)RetargetTypeRefInTypeSig(genericType); + bool anyChange = genericType != newGenericType; + var genericArgs = new List(); + foreach (var arg in gis.GenericArguments) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != arg; + genericArgs.Add(newArg); + } + if (!anyChange) + { + return type; + } + return new GenericInstSig(newGenericType, genericArgs); + } + case ElementType.FnPtr: + { + var fp = type as FnPtrSig; + MethodSig methodSig = fp.MethodSig; + TypeSig newReturnType = RetargetTypeRefInTypeSig(methodSig.RetType); + bool anyChange = newReturnType != methodSig.RetType; + var newArgs = new List(); + foreach (TypeSig arg in methodSig.Params) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != newReturnType; + } + if (!anyChange) + { + return type; + } + var newParamsAfterSentinel = new List(); + foreach (TypeSig arg in methodSig.ParamsAfterSentinel) + { + TypeSig newArg = RetargetTypeRefInTypeSig(arg); + anyChange |= newArg != arg; + newParamsAfterSentinel.Add(newArg); + } + + var newMethodSig = new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newArgs, newParamsAfterSentinel); + return new FnPtrSig(newMethodSig); + } + case ElementType.ByRef: + { + if (next == newNext) + { + return type; + } + return new ByRefSig(newNext); + } + default: + { + return type; + } + } + } + + + public static object RetargetTypeRefInTypeSigOfValue(object oldValue) + { + if (oldValue == null) + { + return null; + } + string typeName = oldValue.GetType().FullName; + if (oldValue.GetType().IsPrimitive) + { + return oldValue; + } + if (oldValue is string || oldValue is UTF8String) + { + return oldValue; + } + if (oldValue is TypeSig typeSig) + { + return RetargetTypeRefInTypeSig(typeSig); + } + if (oldValue is CAArgument caValue) + { + TypeSig newType = RetargetTypeRefInTypeSig(caValue.Type); + object newValue = RetargetTypeRefInTypeSigOfValue(caValue.Value); + if (newType != caValue.Type || newValue != caValue.Value) + { + return new CAArgument(newType, newValue); + } + return oldValue; + } + if (oldValue is List oldArr) + { + bool anyChange = false; + var newArr = new List(); + foreach (CAArgument oldArg in oldArr) + { + if (TryRetargetTypeRefInArgument(oldArg, out var newArg)) + { + anyChange = true; + newArr.Add(newArg); + } + else + { + newArr.Add(oldArg); + } + } + return anyChange ? newArr : oldArr; + } + throw new NotSupportedException($"type:{oldValue.GetType()} value:{oldValue}"); + } + + + + public static bool TryRetargetTypeRefInArgument(CAArgument oldArg, out CAArgument newArg) + { + TypeSig newType = RetargetTypeRefInTypeSig(oldArg.Type); + object newValue = RetargetTypeRefInTypeSigOfValue(oldArg.Value); + if (newType != oldArg.Type || oldArg.Value != newValue) + { + newArg = new CAArgument(newType, newValue); + return true; + } + newArg = default; + return false; + } + + public static bool TryRetargetTypeRefInNamedArgument(CANamedArgument arg) + { + bool anyChange = false; + TypeSig newType = RetargetTypeRefInTypeSig(arg.Type); + if (newType != arg.Type) + { + anyChange = true; + arg.Type = newType; + } + if (TryRetargetTypeRefInArgument(arg.Argument, out var newArg)) + { + arg.Argument = newArg; + anyChange = true; + } + return anyChange; + } + + //public static bool ContainsContainsGenericParameter1(MethodDef method) + //{ + // Assert.IsTrue(!(method.DeclaringType.ContainsGenericParameter || method.MethodSig.ContainsGenericParameter)); + // return false; + //} + + public static bool ContainsContainsGenericParameter1(MethodSpec methodSpec) + { + if (methodSpec.GenericInstMethodSig.ContainsGenericParameter) + { + return true; + } + IMethodDefOrRef method = methodSpec.Method; + if (method.IsMethodDef) + { + return false;// ContainsContainsGenericParameter1((MethodDef)method); + } + if (method.IsMemberRef) + { + return ContainsContainsGenericParameter1((MemberRef)method); + } + throw new Exception($"unknown method: {method}"); + } + + public static bool ContainsContainsGenericParameter1(MemberRef memberRef) + { + IMemberRefParent parent = memberRef.Class; + if (parent is TypeSpec typeSpec) + { + return typeSpec.ContainsGenericParameter; + } + return false; + } + + public static bool ContainsContainsGenericParameter(IMethod method) + { + Assert.IsTrue(method.IsMethod); + if (method is MethodDef methodDef) + { + return false; + } + + if (method is MethodSpec methodSpec) + { + return ContainsContainsGenericParameter1(methodSpec); + } + if (method is MemberRef memberRef) + { + return ContainsContainsGenericParameter1(memberRef); + } + throw new Exception($"unknown method: {method}"); + } + + + + public static TypeSig Inflate(TypeSig sig, GenericArgumentContext ctx) + { + if (ctx == null || !sig.ContainsGenericParameter) + { + return sig; + } + return ctx.Resolve(sig); + } + + public static IList TryInflate(IList sig, GenericArgumentContext ctx) + { + if (sig == null || ctx == null) + { + return sig; + } + return sig.Select(s => Inflate(s, ctx)).ToList() ?? null; + } + + + public static MethodSig InflateMethodSig(MethodSig methodSig, GenericArgumentContext genericArgumentContext) + { + var newReturnType = Inflate(methodSig.RetType, genericArgumentContext); + var newParams = new List(); + foreach (var param in methodSig.Params) + { + newParams.Add(Inflate(param, genericArgumentContext)); + } + var newParamsAfterSentinel = new List(); + if (methodSig.ParamsAfterSentinel != null) + { + throw new NotSupportedException($"methodSig.ParamsAfterSentinel is not supported: {methodSig}"); + //foreach (var param in methodSig.ParamsAfterSentinel) + //{ + // newParamsAfterSentinel.Add(Inflate(param, genericArgumentContext)); + //} + } + return new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newParams, null); + } + + public static IList GetGenericArguments(IMemberRefParent type) + { + if (type is TypeDef typeDef) + { + return null; + } + if (type is TypeRef typeRef) + { + return null; + } + if (type is TypeSpec typeSpec) + { + GenericInstSig genericInstSig = typeSpec.TypeSig.ToGenericInstSig(); + return genericInstSig?.GenericArguments; + } + throw new NotSupportedException($"type:{type}"); + } + + public static GenericArgumentContext GetInflatedMemberRefGenericArgument(IMemberRefParent type, GenericArgumentContext ctx) + { + if (type is TypeDef typeDef) + { + return null; + } + if (type is TypeRef typeRef) + { + return null; + } + if (type is TypeSpec typeSpec) + { + GenericInstSig genericInstSig = typeSpec.TypeSig.ToGenericInstSig(); + if (genericInstSig == null) + { + return ctx; + } + return new GenericArgumentContext(TryInflate(genericInstSig.GenericArguments, ctx), null); + } + throw new NotSupportedException($"type:{type}"); + } + + public static MethodSig GetInflatedMethodSig(IMethod method, GenericArgumentContext ctx) + { + if (method is MethodDef methodDef) + { + return methodDef.MethodSig; + } + if (method is MemberRef memberRef) + { + return InflateMethodSig(memberRef.MethodSig, GetInflatedMemberRefGenericArgument(memberRef.Class, ctx)); + } + if (method is MethodSpec methodSpec) + { + var genericInstMethodSig = methodSpec.GenericInstMethodSig; + if (methodSpec.Method is MethodDef methodDef2) + { + return InflateMethodSig(methodDef2.MethodSig, new GenericArgumentContext(null, TryInflate(genericInstMethodSig.GenericArguments, ctx))); + } + if (methodSpec.Method is MemberRef memberRef2) + { + return InflateMethodSig(memberRef2.MethodSig, new GenericArgumentContext( + GetInflatedMemberRefGenericArgument(memberRef2.Class, ctx)?.typeArgsStack, + TryInflate(genericInstMethodSig.GenericArguments, ctx))); + } + + } + throw new NotSupportedException($" method: {method}"); + } + + public static TypeSig InflateFieldSig(IField field, GenericArgumentContext ctx) + { + if (field is FieldDef fieldDef) + { + return fieldDef.FieldType; + } + if (field is MemberRef memberRef) + { + return Inflate(memberRef.FieldSig.Type, new GenericArgumentContext(TryInflate(GetGenericArguments(memberRef.Class), ctx), null)); + } + + throw new Exception($"unknown field:{field}"); + } + + public static ThisArgType GetThisArgType(IMethod method) + { + if (!method.MethodSig.HasThis) + { + return ThisArgType.None; + } + if (method is MethodDef methodDef) + { + return methodDef.DeclaringType.IsValueType ? ThisArgType.ValueType : ThisArgType.Class; + } + if (method is MemberRef memberRef) + { + TypeDef typeDef = MetaUtil.GetMemberRefTypeDefParentOrNull(memberRef.Class); + if (typeDef == null) + { + return ThisArgType.Class; + } + return typeDef.IsValueType ? ThisArgType.ValueType : ThisArgType.Class; + } + if (method is MethodSpec methodSpec) + { + return GetThisArgType(methodSpec.Method); + } + throw new NotSupportedException($" method: {method}"); + } + + public static MethodSig ToSharedMethodSig(ICorLibTypes corTypes, MethodSig methodSig) + { + var newReturnType = methodSig.RetType; + var newParams = new List(); + foreach (var param in methodSig.Params) + { + newParams.Add(ToShareTypeSig(corTypes, param)); + } + if (methodSig.ParamsAfterSentinel != null) + { + //foreach (var param in methodSig.ParamsAfterSentinel) + //{ + // newParamsAfterSentinel.Add(ToShareTypeSig(corTypes, param)); + //} + throw new NotSupportedException($"methodSig.ParamsAfterSentinel is not supported: {methodSig}"); + } + return new MethodSig(methodSig.CallingConvention, methodSig.GenParamCount, newReturnType, newParams, null); + } + + public static TypeSig ToShareTypeSig(ICorLibTypes corTypes, TypeSig typeSig) + { + var a = typeSig.RemovePinnedAndModifiers(); + switch (a.ElementType) + { + case ElementType.Void: return corTypes.Void; + case ElementType.Boolean: return corTypes.Byte; + case ElementType.Char: return corTypes.UInt16; + case ElementType.I1: return corTypes.SByte; + case ElementType.U1: return corTypes.Byte; + case ElementType.I2: return corTypes.Int16; + case ElementType.U2: return corTypes.UInt16; + case ElementType.I4: return corTypes.Int32; + case ElementType.U4: return corTypes.UInt32; + case ElementType.I8: return corTypes.Int64; + case ElementType.U8: return corTypes.UInt64; + case ElementType.R4: return corTypes.Single; + case ElementType.R8: return corTypes.Double; + case ElementType.String: return corTypes.Object; + case ElementType.TypedByRef: return corTypes.TypedReference; + case ElementType.I: return corTypes.IntPtr; + case ElementType.U: return corTypes.UIntPtr; + case ElementType.Object: return corTypes.Object; + case ElementType.Sentinel: return typeSig; + case ElementType.Ptr: return corTypes.UIntPtr; + case ElementType.ByRef: return corTypes.UIntPtr; + case ElementType.SZArray: return typeSig; + case ElementType.Array: return typeSig; + case ElementType.ValueType: + { + TypeDef typeDef = a.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + return typeSig; + } + case ElementType.Var: + case ElementType.MVar: + case ElementType.Class: return corTypes.Object; + case ElementType.GenericInst: + { + var gia = (GenericInstSig)a; + TypeDef typeDef = gia.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + if (!typeDef.IsValueType) + { + return corTypes.Object; + } + // il2cpp will raise error when try to share generic value type + return typeSig; + //return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList()); + } + case ElementType.FnPtr: return corTypes.UIntPtr; + case ElementType.ValueArray: return typeSig; + case ElementType.Module: return typeSig; + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + + public static void AppendIl2CppStackTraceNameOfTypeSig(StringBuilder sb, TypeSig typeSig) + { + typeSig = typeSig.RemovePinnedAndModifiers(); + + switch (typeSig.ElementType) + { + case ElementType.Void: sb.Append("Void"); break; + case ElementType.Boolean: sb.Append("Boolean"); break; + case ElementType.Char: sb.Append("Char"); break; + case ElementType.I1: sb.Append("SByte"); break; + case ElementType.U1: sb.Append("Byte"); break; + case ElementType.I2: sb.Append("Int16"); break; + case ElementType.U2: sb.Append("UInt16"); break; + case ElementType.I4: sb.Append("Int32"); break; + case ElementType.U4: sb.Append("UInt32"); break; + case ElementType.I8: sb.Append("Int64"); break; + case ElementType.U8: sb.Append("UInt64"); break; + case ElementType.R4: sb.Append("Single"); break; + case ElementType.R8: sb.Append("Double"); break; + case ElementType.String: sb.Append("String"); break; + case ElementType.Ptr: AppendIl2CppStackTraceNameOfTypeSig(sb, typeSig.Next); sb.Append("*"); break; + case ElementType.ByRef: AppendIl2CppStackTraceNameOfTypeSig(sb, typeSig.Next); sb.Append("&"); break; + case ElementType.ValueType: + case ElementType.Class: + { + var classOrValueTypeSig = (ClassOrValueTypeSig)typeSig; + TypeDef typeDef = classOrValueTypeSig.TypeDefOrRef.ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{classOrValueTypeSig} definition could not be found"); + } + sb.Append(typeDef.Name); + break; + } + case ElementType.GenericInst: + { + var genericInstSig = (GenericInstSig)typeSig; + AppendIl2CppStackTraceNameOfTypeSig(sb, genericInstSig.GenericType); + break; + } + case ElementType.Var: + case ElementType.MVar: + { + var varSig = (GenericSig)typeSig; + sb.Append(varSig.GenericParam.Name); + break; + } + case ElementType.I: sb.Append("IntPtr"); break; + case ElementType.U: sb.Append("UIntPtr"); break; + case ElementType.FnPtr: sb.Append("IntPtr"); break; + case ElementType.Object: sb.Append("Object"); break; + case ElementType.SZArray: + { + var szArraySig = (SZArraySig)typeSig; + AppendIl2CppStackTraceNameOfTypeSig(sb, szArraySig.Next); + sb.Append("[]"); + break; + } + case ElementType.Array: + { + var arraySig = (ArraySig)typeSig; + AppendIl2CppStackTraceNameOfTypeSig(sb, arraySig.Next); + sb.Append("["); + for (int i = 0; i < arraySig.Rank - 1; i++) + { + sb.Append(","); + } + sb.Append("]"); + break; + } + case ElementType.TypedByRef: sb.Append("TypedReference"); break; + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + public static TypeDef GetRootDeclaringType(TypeDef type) + { + TypeDef cur = type; + while (true) + { + TypeDef declaringType = cur.DeclaringType; + if (declaringType == null) + { + return cur; + } + cur = declaringType; + } + } + + public static string CreateMethodDefIl2CppStackTraceSignature(MethodDef method) + { + var result = new StringBuilder(); + TypeDef declaringType = method.DeclaringType; + + string namespaze = GetRootDeclaringType(declaringType).Namespace; + if (!string.IsNullOrEmpty(namespaze)) + { + result.Append(namespaze); + result.Append("."); + } + result.Append(declaringType.Name); + result.Append(":"); + result.Append(method.Name); + result.Append("("); + + int index = 0; + foreach (TypeSig p in method.GetParams()) + { + if (index > 0) + { + result.Append(", "); + } + AppendIl2CppStackTraceNameOfTypeSig(result, p); + ++index; + } + result.Append(")"); + return result.ToString(); + } + + public static bool HasCompilerGeneratedAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Find(ConstValues.CompilerGeneratedAttributeFullName) != null; + } + + public static bool HasEncryptFieldAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Find(ConstValues.EncryptFieldAttributeFullName) != null; + } + + public static bool HasRuntimeInitializeOnLoadMethodAttribute(MethodDef method) + { + return method.CustomAttributes.Find(ConstValues.RuntimeInitializedOnLoadMethodAttributeFullName) != null; + } + + public static bool HasBlackboardEnumAttribute(TypeDef typeDef) + { + return typeDef.CustomAttributes.Find(ConstValues.BlackboardEnumAttributeFullName) != null; + } + + public static bool HasBurstCompileAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Find(ConstValues.BurstCompileFullName) != null; + } + + public static bool HasDOTSCompilerGeneratedAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Find(ConstValues.DOTSCompilerGeneratedAttributeFullName) != null; + } + + public static bool HasMicrosoftCodeAnalysisEmbeddedAttribute(IHasCustomAttribute obj) + { + return obj.CustomAttributes.Find(ConstValues.EmbeddedAttributeFullName) != null; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta new file mode 100644 index 00000000..9ebbd424 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/MetaUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ded544371a7eb524caa1ccef3daebe55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs new file mode 100644 index 00000000..8f6d5d68 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs @@ -0,0 +1,43 @@ +using System.Text.RegularExpressions; + +namespace Obfuz.Utils +{ + public class NameMatcher + { + private readonly string _str; + private readonly Regex _regex; + + public string NameOrPattern => _str; + + public bool IsWildcardPattern => _regex != null; + + public NameMatcher(string nameOrPattern) + { + if (string.IsNullOrEmpty(nameOrPattern)) + { + nameOrPattern = "*"; + } + _str = nameOrPattern; + _regex = nameOrPattern.Contains("*") || nameOrPattern.Contains("?") ? new Regex(WildcardToRegex(nameOrPattern)) : null; + } + + public static string WildcardToRegex(string pattern) + { + return "^" + Regex.Escape(pattern). + Replace("\\*", ".*"). + Replace("\\?", ".") + "$"; + } + + public bool IsMatch(string name) + { + if (_regex != null) + { + return _regex.IsMatch(name); + } + else + { + return _str == name; + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta new file mode 100644 index 00000000..6abe4e16 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NameMatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3a646fe086ecbd4a8dbf36a395ada71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs new file mode 100644 index 00000000..90d55e0b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs @@ -0,0 +1,14 @@ +namespace Obfuz.Utils +{ + public class NumberRange where T : struct + { + public readonly T? min; + public readonly T? max; + + public NumberRange(T? min, T? max) + { + this.min = min; + this.max = max; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta new file mode 100644 index 00000000..f1b17e16 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/NumberRange.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d147a6853ce57c4d88529fb73823435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs new file mode 100644 index 00000000..162cfdeb --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs @@ -0,0 +1,243 @@ +using dnlib.DotNet; +using Obfuz.Editor; +using System.Linq; + +namespace Obfuz.Utils +{ + public class ObfuzIgnoreScopeComputeCache + { + private readonly CachedDictionary _selfObfuzIgnoreScopeCache; + private readonly CachedDictionary _enclosingObfuzIgnoreScopeCache; + private readonly CachedDictionary _selfObfuzIgnoreApplyToChildTypesScopeCache; + private readonly CachedDictionary _inheritedObfuzIgnoreScopeCache; + + public ObfuzIgnoreScopeComputeCache() + { + _selfObfuzIgnoreScopeCache = new CachedDictionary(GetObfuzIgnoreScope); + _enclosingObfuzIgnoreScopeCache = new CachedDictionary(GetEnclosingObfuzIgnoreScope); + _selfObfuzIgnoreApplyToChildTypesScopeCache = new CachedDictionary(GetObfuzIgnoreScopeApplyToChildTypes); + _inheritedObfuzIgnoreScopeCache = new CachedDictionary(GetInheritObfuzIgnoreScope); + } + + private ObfuzScope? GetObfuzIgnoreScope(IHasCustomAttribute obj) + { + var ca = obj.CustomAttributes.FirstOrDefault(c => c.AttributeType.FullName == ConstValues.ObfuzIgnoreAttributeFullName); + if (ca == null) + { + return null; + } + var scope = (ObfuzScope)ca.ConstructorArguments[0].Value; + return scope; + } + + private ObfuzScope? GetEnclosingObfuzIgnoreScope(TypeDef typeDef) + { + TypeDef cur = typeDef.DeclaringType; + while (cur != null) + { + var ca = cur.CustomAttributes?.FirstOrDefault(c => c.AttributeType.FullName == ConstValues.ObfuzIgnoreAttributeFullName); + if (ca != null) + { + var scope = (ObfuzScope)ca.ConstructorArguments[0].Value; + CANamedArgument inheritByNestedTypesArg = ca.GetNamedArgument("ApplyToNestedTypes", false); + bool inheritByNestedTypes = inheritByNestedTypesArg == null || (bool)inheritByNestedTypesArg.Value; + return inheritByNestedTypes ? (ObfuzScope?)scope : null; + } + cur = cur.DeclaringType; + } + return null; + } + + private ObfuzScope? GetObfuzIgnoreScopeApplyToChildTypes(TypeDef cur) + { + if (cur.Module.IsCoreLibraryModule == true) + { + return null; + } + var ca = cur.CustomAttributes?.FirstOrDefault(c => c.AttributeType.FullName == ConstValues.ObfuzIgnoreAttributeFullName); + if (ca != null) + { + var scope = (ObfuzScope)ca.ConstructorArguments[0].Value; + CANamedArgument inheritByChildTypesArg = ca.GetNamedArgument("ApplyToChildTypes", false); + bool inheritByChildTypes = inheritByChildTypesArg != null && (bool)inheritByChildTypesArg.Value; + if (inheritByChildTypes) + { + return scope; + } + } + return null; + } + + private ObfuzScope? GetInheritObfuzIgnoreScope(TypeDef typeDef) + { + TypeDef cur = typeDef; + for (; cur != null; cur = MetaUtil.GetBaseTypeDef(cur)) + { + ObfuzScope? scope = _selfObfuzIgnoreApplyToChildTypesScopeCache.GetValue(cur); + if (scope != null) + { + return scope; + } + foreach (var interfaceType in cur.Interfaces) + { + TypeDef interfaceTypeDef = interfaceType.Interface.ResolveTypeDef(); + if (interfaceTypeDef != null) + { + ObfuzScope? interfaceScope = _selfObfuzIgnoreApplyToChildTypesScopeCache.GetValue(interfaceTypeDef); + if (interfaceScope != null) + { + return interfaceScope; + } + } + } + } + return null; + } + + //private ObfuzScope? GetSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope((IHasCustomAttribute obj, TypeDef declaringType) objAndDeclaringType, ObfuzScope targetScope) + //{ + // ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(objAndDeclaringType.obj); + // if (scope != null) + // { + // return scope; + // } + // if (objAndDeclaringType.declaringType == null) + // { + // return null; + // } + // ObfuzScope? declaringOrEnclosingScope = _selfObfuzIgnoreScopeCache.GetValue(declaringType) ?? _enclosingObfuzIgnoreScopeCache.GetValue(declaringType) ?? _inheritedObfuzIgnoreScopeCache.GetValue(declaringType); + // return declaringOrEnclosingScope != null && (declaringOrEnclosingScope & targetScope) != 0; + //} + + //private bool HasObfuzIgnoreScope(IHasCustomAttribute obj, ObfuzScope targetScope) + //{ + // ObfuzScope? objScope = _selfObfuzIgnoreScopeCache.GetValue(obj); + // return objScope != null && (objScope & targetScope) != 0; + //} + + //private bool HasDeclaringOrEnclosingOrInheritObfuzIgnoreScope(TypeDef typeDef, ObfuzScope targetScope) + //{ + // if (typeDef == null) + // { + // return false; + // } + // ObfuzScope? declaringOrEnclosingScope = _selfObfuzIgnoreScopeCache.GetValue(typeDef) ?? _enclosingObfuzIgnoreScopeCache.GetValue(typeDef) ?? _inheritedObfuzIgnoreScopeCache.GetValue(typeDef); + // return declaringOrEnclosingScope != null && (declaringOrEnclosingScope & targetScope) != 0; + //} + + public ObfuzScope? GetSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(IHasCustomAttribute obj, TypeDef declaringType) + { + ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(obj); + if (scope != null) + { + return scope; + } + if (declaringType == null) + { + return null; + } + ObfuzScope? declaringOrEnclosingScope = _selfObfuzIgnoreScopeCache.GetValue(declaringType) ?? _enclosingObfuzIgnoreScopeCache.GetValue(declaringType) ?? _inheritedObfuzIgnoreScopeCache.GetValue(declaringType); + return declaringOrEnclosingScope; + } + + public bool HasSelfOrEnclosingOrInheritObfuzIgnoreScope(TypeDef typeDef, ObfuzScope targetScope) + { + ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(typeDef) ?? _enclosingObfuzIgnoreScopeCache.GetValue(typeDef) ?? _inheritedObfuzIgnoreScopeCache.GetValue(typeDef); + return scope != null && (scope & targetScope) != 0; + } + + public bool HasSelfOrDeclaringOrEnclosingOrInheritObfuzIgnoreScope(IHasCustomAttribute obj, TypeDef declaringType, ObfuzScope targetScope) + { + ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(obj); + if (scope != null) + { + return (scope & targetScope) != 0; + } + if (declaringType == null) + { + return false; + } + ObfuzScope? declaringOrEnclosingScope = _selfObfuzIgnoreScopeCache.GetValue(declaringType) ?? _enclosingObfuzIgnoreScopeCache.GetValue(declaringType) ?? _inheritedObfuzIgnoreScopeCache.GetValue(declaringType); + return declaringOrEnclosingScope != null && (declaringOrEnclosingScope & targetScope) != 0; + } + + public bool HasSelfOrInheritPropertyOrEventOrOrTypeDefObfuzIgnoreScope(MethodDef obj, ObfuzScope targetScope) + { + ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(obj); + if (scope != null && (scope & targetScope) != 0) + { + return true; + } + + TypeDef declaringType = obj.DeclaringType; + ObfuzScope? declaringOrEnclosingScope = _selfObfuzIgnoreScopeCache.GetValue(declaringType) ?? _enclosingObfuzIgnoreScopeCache.GetValue(declaringType) ?? _inheritedObfuzIgnoreScopeCache.GetValue(declaringType); + + foreach (var propertyDef in declaringType.Properties) + { + if (propertyDef.GetMethod == obj || propertyDef.SetMethod == obj) + { + ObfuzScope? finalScope = _selfObfuzIgnoreScopeCache.GetValue(propertyDef); + if (finalScope != null && (finalScope & targetScope) != 0) + { + return true; + } + break; + } + } + + foreach (var eventDef in declaringType.Events) + { + if (eventDef.AddMethod == obj || eventDef.RemoveMethod == obj) + { + ObfuzScope? finalScope = _selfObfuzIgnoreScopeCache.GetValue(eventDef); + if (finalScope != null && (finalScope & targetScope) != 0) + { + return true; + } + break; + } + } + + return declaringOrEnclosingScope != null && (declaringOrEnclosingScope & targetScope) != 0; + } + + public bool HasSelfOrInheritPropertyOrEventOrOrTypeDefIgnoreMethodName(MethodDef obj) + { + ObfuzScope? scope = _selfObfuzIgnoreScopeCache.GetValue(obj); + if (scope != null && (scope & ObfuzScope.MethodName) != 0) + { + return true; + } + + TypeDef declaringType = obj.DeclaringType; + + foreach (var propertyDef in declaringType.Properties) + { + if (propertyDef.GetMethod == obj || propertyDef.SetMethod == obj) + { + ObfuzScope? finalScope = GetObfuzIgnoreScope(propertyDef); + if (finalScope != null && (finalScope & ObfuzScope.PropertyGetterSetterName) != 0) + { + return true; + } + break; + } + } + + foreach (var eventDef in declaringType.Events) + { + if (eventDef.AddMethod == obj || eventDef.RemoveMethod == obj) + { + ObfuzScope? finalScope = GetObfuzIgnoreScope(eventDef); + if (finalScope != null && (finalScope & ObfuzScope.EventAddRemoveFireName) != 0) + { + return true; + } + break; + } + } + + return HasSelfOrEnclosingOrInheritObfuzIgnoreScope(declaringType, ObfuzScope.MethodName | ObfuzScope.PropertyGetterSetterName | ObfuzScope.EventAddRemoveFireName); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs.meta new file mode 100644 index 00000000..fdc7e014 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ObfuzIgnoreScopeComputeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f64b9a72981c0e45a03501db02e6538 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs new file mode 100644 index 00000000..d2e02e8f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace Obfuz.Utils +{ + public class PathAssemblyResolver : AssemblyResolverBase + { + private readonly string[] _searchPaths; + + public PathAssemblyResolver(params string[] searchPaths) + { + _searchPaths = searchPaths; + } + + public override string ResolveAssembly(string assemblyName) + { + foreach (var path in _searchPaths) + { + string assPath = Path.Combine(path, assemblyName + ".dll"); + if (File.Exists(assPath)) + { + //Debug.Log($"resolve {assemblyName} at {assPath}"); + return assPath; + } + } + return null; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta new file mode 100644 index 00000000..1ae9c58f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PathAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a7681737885f604e885ee39d0bedd74 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs new file mode 100644 index 00000000..94f41e75 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs @@ -0,0 +1,13 @@ +using UnityEditor; + +namespace Obfuz.Utils +{ + public static class PlatformUtil + { + public static bool IsMonoBackend() + { + return PlayerSettings.GetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup) + == ScriptingImplementation.Mono2x; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta new file mode 100644 index 00000000..ec195e6d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/PlatformUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85d01014c084c56498d292d3b16351d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs new file mode 100644 index 00000000..0f542995 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Obfuz.Utils +{ + static class RandomUtil + { + public static void ShuffleList(List list, IRandom random) + { + int n = list.Count; + for (int i = n - 1; i > 0; i--) + { + int j = random.NextInt(i + 1); + T temp = list[i]; + list[i] = list[j]; + list[j] = temp; + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs.meta new file mode 100644 index 00000000..961f1be9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d482c078394711d428e627843d2481d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs new file mode 100644 index 00000000..e66bfc64 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs @@ -0,0 +1,63 @@ +namespace Obfuz.Utils +{ + public class RandomWithKey : IRandom + { + private const long a = 1664525; + private const long c = 1013904223; + private const long m = 4294967296; // 2^32 + + private readonly int[] _key; + + private int _nextIndex; + + private int _seed; + + public RandomWithKey(int[] key, int seed) + { + _key = key; + _seed = seed; + } + + public int[] Key => _key; + + public int NextInt(int min, int max) + { + return min + NextInt(max - min); + } + + public int NextInt(int max) + { + return (int)((uint)NextInt() % (uint)max); + } + + private int GetNextSalt() + { + if (_nextIndex >= _key.Length) + { + _nextIndex = 0; + } + return _key[_nextIndex++]; + } + + public int NextInt() + { + _seed = (int)((a * _seed + c) % m); + return _seed ^ GetNextSalt(); + } + + public long NextLong() + { + return ((long)NextInt() << 32) | (uint)NextInt(); + } + + public float NextFloat() + { + return (float)((double)(uint)NextInt() / uint.MaxValue); + } + + public bool NextInPercentage(float percentage) + { + return NextFloat() < percentage; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta new file mode 100644 index 00000000..41b7f75e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/RandomWithKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e16d7eb75fe2354d96eca5bb01358a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs new file mode 100644 index 00000000..f15140da --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Obfuz.Utils +{ + public static class ReflectionUtil + { + public static List FindTypesInCurrentAppDomain(string fullName) + { + return AppDomain.CurrentDomain.GetAssemblies() + .Select(assembly => assembly.GetType(fullName)) + .Where(type => type != null) + .ToList(); + } + + public static Type FindUniqueTypeInCurrentAppDomain(string fullName) + { + var foundTypes = FindTypesInCurrentAppDomain(fullName); + if (foundTypes.Count == 0) + { + throw new Exception($"class {fullName} not found in any assembly!"); + } + if (foundTypes.Count > 1) + { + throw new Exception($"class {fullName} found in multiple assemblies! Please retain only one!"); + } + return foundTypes[0]; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs.meta new file mode 100644 index 00000000..05d723b9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ReflectionUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6fac8216afeffb746b1b67d1f16883b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs new file mode 100644 index 00000000..3f008ccc --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs @@ -0,0 +1,9 @@ +namespace Obfuz.Utils +{ + public enum ThisArgType + { + None, + ValueType, + Class, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta new file mode 100644 index 00000000..c73587a7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/ThisArgType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d5a6303cdb66374f95187ca31b5e82f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs new file mode 100644 index 00000000..5b80674f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs @@ -0,0 +1,219 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Obfuz.Utils +{ + public static class TypeSigUtil + { + public static string ComputeTypeDefSignature(TypeDef type) + { + return type.FullName; + } + + public static string ComputeMethodDefSignature(MethodDef method) + { + var result = new StringBuilder(); + ComputeTypeSigName(method.MethodSig.RetType, result); + result.Append(" "); + result.Append(method.DeclaringType.FullName); + result.Append("::"); + if (method.IsStatic) + { + result.Append("@"); + } + result.Append(method.Name); + if (method.HasGenericParameters) + { + result.Append($"`{method.GenericParameters.Count}"); + } + result.Append("("); + for (int i = 0; i < method.Parameters.Count; i++) + { + if (i > 0) + { + result.Append(", "); + } + ComputeTypeSigName(method.Parameters[i].Type, result); + } + result.Append(")"); + return result.ToString(); + } + + public static string ComputeFieldDefSignature(FieldDef field) + { + var result = new StringBuilder(); + ComputeTypeSigName(field.FieldSig.Type, result); + result.Append(" "); + result.Append(field.Name); + return result.ToString(); + } + + public static string ComputePropertyDefSignature(PropertyDef property) + { + var result = new StringBuilder(); + + PropertySig propertySig = property.PropertySig; + ComputeTypeSigName(propertySig.RetType, result); + result.Append(" "); + result.Append(property.Name); + + IList parameters = propertySig.Params; + if (parameters.Count > 0) + { + result.Append("("); + + for (int i = 0; i < parameters.Count; i++) + { + if (i > 0) + { + result.Append(", "); + } + ComputeTypeSigName(parameters[i], result); + } + result.Append(")"); + } + + return result.ToString(); + } + + public static string ComputeEventDefSignature(EventDef eventDef) + { + var result = new StringBuilder(); + ComputeTypeSigName(eventDef.EventType.ToTypeSig(), result); + result.Append(" "); + result.Append(eventDef.Name); + return result.ToString(); + } + + public static string ComputeMethodSpecSignature(TypeSig type) + { + var sb = new StringBuilder(); + ComputeTypeSigName(type, sb); + return sb.ToString(); + } + + public static void ComputeTypeSigName(TypeSig type, StringBuilder result) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: result.Append("void"); break; + case ElementType.Boolean: result.Append("bool"); break; + case ElementType.Char: result.Append("char"); break; + case ElementType.I1: result.Append("sbyte"); break; + case ElementType.U1: result.Append("byte"); break; + case ElementType.I2: result.Append("short"); break; + case ElementType.U2: result.Append("ushort"); break; + case ElementType.I4: result.Append("int"); break; + case ElementType.U4: result.Append("uint"); break; + case ElementType.I8: result.Append("long"); break; + case ElementType.U8: result.Append("ulong"); break; + case ElementType.R4: result.Append("float"); break; + case ElementType.R8: result.Append("double"); break; + case ElementType.String: result.Append("string"); break; + case ElementType.Ptr: + ComputeTypeSigName(((PtrSig)type).Next, result); + result.Append("*"); + break; + case ElementType.ByRef: + ComputeTypeSigName(((ByRefSig)type).Next, result); + result.Append("&"); + break; + case ElementType.ValueType: + case ElementType.Class: + { + var valueOrClassType = type.ToClassOrValueTypeSig(); + var typeDef = valueOrClassType.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.Module.IsCoreLibraryModule != true) + { + result.Append($"[{typeDef.Module.Assembly.Name}]"); + } + result.Append(typeDef.FullName); + break; + } + case ElementType.GenericInst: + { + var genInst = (GenericInstSig)type; + ComputeTypeSigName(genInst.GenericType, result); + result.Append("<"); + for (int i = 0; i < genInst.GenericArguments.Count; i++) + { + if (i > 0) + { + result.Append(","); + } + ComputeTypeSigName(genInst.GenericArguments[i], result); + } + result.Append(">"); + break; + } + case ElementType.SZArray: + ComputeTypeSigName(((SZArraySig)type).Next, result); + result.Append("[]"); + break; + case ElementType.Array: + { + var arraySig = (ArraySig)type; + ComputeTypeSigName(arraySig.Next, result); + result.Append("["); + for (int i = 0; i < arraySig.Rank; i++) + { + if (i > 0) + { + result.Append(","); + } + //result.Append(arraySig.Sizes[i]); + } + result.Append("]"); + break; + } + case ElementType.FnPtr: + { + var fnPtr = (FnPtrSig)type; + result.Append("("); + MethodSig ms = fnPtr.MethodSig; + ComputeTypeSigName(ms.RetType, result); + result.Append("("); + for (int i = 0; i < ms.Params.Count; i++) + { + if (i > 0) + { + result.Append(","); + } + ComputeTypeSigName(ms.Params[i], result); + } + result.Append(")*"); + break; + } + case ElementType.TypedByRef: + result.Append("typedref"); + break; + case ElementType.I: + result.Append("nint"); + break; + case ElementType.U: + result.Append("nuint"); + break; + case ElementType.Object: + result.Append("object"); + break; + case ElementType.Var: + { + var var = (GenericVar)type; + result.Append($"!{var.Number}"); + break; + } + case ElementType.MVar: + { + var mvar = (GenericMVar)type; + result.Append($"!!{mvar.Number}"); + break; + } + default: throw new NotSupportedException($"[ComputeTypeSigName] not support :{type}"); + + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta new file mode 100644 index 00000000..fd925122 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Editor/Utils/TypeSigUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 83e1214102577b449a933438c41c97bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE b/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE new file mode 100644 index 00000000..093e5999 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Code Philosophy(代码哲学) + +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. diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE.meta b/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE.meta new file mode 100644 index 00000000..3bb31e64 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4431597180c05fb46839ded925d40a19 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Plugins.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Plugins.meta new file mode 100644 index 00000000..074840ee --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f8b2842c597aa4249b275ef7cb2200ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Plugins/dnlib.dll b/UnityProject/Packages/com.code-philosophy.obfuz/Plugins/dnlib.dll new file mode 100644 index 0000000000000000000000000000000000000000..d6c2fd994cd746dcbe3c355d4e98c0839d81f519 GIT binary patch literal 1263616 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

b|r)=+1PvhhKsWm3>q;s8dZUo2gSxD%)PC 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

P>{7QMz$Mw8scaSd;Mv5m;+ zQ%2IRE6Z$dC_R;&EKYMQ&!T2f4VR^l%i5J5RuRHg^OX6n1<2;GWZ)9*|(vNmhS;AY%ozC+a zu1Bk!iC_%O-7kz7(cfdOHIJP#+tYWXhw$xGRtW0ALM&Bc0TQN?;>)x>&aS;Tr|-NSlhVZ(Z4<-&SoX~KGB?ZJ9valv|I z^}u>$dBCh;>pYAotRzJlPA_thnx6J%4?bqckaZH$Lyx`$r4b!75qLh$Qk>nS0yD#s zY0Vt?F~*t$zj`#MNQqR+A#PoJFLG@kxu&k}R4Q{oi5#K$WdWFh0(&!U_$ zw$8fVD3;Z+iaNi93rE;^3{OVcR`RYa5MnxbcTTmiPh zrA;~m#$JROxeb243Fg5LPp@^ZY>Is8M$TaHumLc*hZ%}i&eA7c^pUxKqa2+P!ZFSsw80HumO(5(vp}i896JVizT58=Rl};%|}q$`T$Ni0zX%3<5M`i z-569B;yc2QGvh}#OnW@d4__u`LH8syW9!8pJ_X6bQslX{e;{`9<-1vc4DvE$tLRf(kx&jF*l}%RstnA)rlTb=FWwatyYL%{1 zx<2h{>%rxFR~nI=co@8 zz)H*L0hUce^c-P89xzDCpAn=%?1K^{Rg ziy=tUFtnEWBK7GhUsJZg0v_@<6W>(lfXPPPSdgc&5T*n=$2#o7cpR!4w(B` zeltaUEMNFHj^)Q*eJnrr>SOt_S0Brdz4};w?A6Ee#arBt8euG~IhHRuSZ4%v5FQ89 z6SfAgZ4QQKMlfcHjo9$$V5Kdz>ZX+JUQl07v|z%Q)2diP5C6&w>g8)HfA(n$Tn5M=goKGrh?aPO$jDh8PK zz0TSNSQ-m=wuVyLgI9idB(%=ThHz;GHk=+CE_DyGQO5Vc<-dlEh>1}{V8yDjhiExv zXvD&3BCujz1~}Xo&5-we+-M8E%iy)5UM@`Rx7m5l!V38^gkX^~q8nX-pP{6T5XzY* zoSe+4+a_}%^}D99ubSS0jK+78=#lm+Kxbi|JcZE5E7>TbI~fTL@ETIhaUn_Y2*1{H z5detS?l4-0D?nT;qlMPK$+BssdkrQn zbJJbh;MZr*V$bB&MAN@Jnh|}%JEWx8A88!y4!=Q6<4m_m*L0?v+vzgT#vEJz;p`$% zdp^_c*4Z{^pW|km$G1D#l6ica?Nue@;I#hufUf5k*puAfK|(N&V)s}<#KymBmK^ut z+@ika2;$jBT@p2hTvFbHAs4Z=A=lDrbJQfF9@Ur@3S0ALEtftkeG$f3wdKn=vsd*4 z^jYaQfk2<;hW#XUw4(@Xyj;#o^Vgh}CTPz}9|#sLXQgSXp5?4G(^28SL1m4z(oAmd zv(mIy@mXoWoB!9&Z~3?@8o({PGL4iL8H9 z*6T^eC#=sjC#+Mso^-h{o64qd!h|N74Ma+un8v**9$C+(aOm(+9DWu_QaDK+VX6>h z@gNLNVq+eM;M>#s5?7a8;=-gTtxsoGKgt|pB!&Ib%Bd-v_BIUj0(-!l;vFdB$>H#x zOg1A!BfVLc&1T|+GqV%EKN(?|7;JwVa$2h1XH)BG&$i29?KO8no!R#4_hU_WWIHs& zLqw;2TBjZTwthVUN>;}|iHr5~Zu%Egd+9bQxxKmw7+s=sa9=6h`>R;Axis6cV2|7z zT;+`o<^0C^%g^n7Hzn(}7b6!BaLXgas5gi(MhqgaJ1s&0Al_*a0sw)HaG5;bt^<0*$5H3?I;N8M zvY~Yc4-rGc`mLbBH8OxT*rlNUcKW5M)XBzx-EOc4&#F`xQ*mw=plm^>g@1eH6s z$b)kUbfeGng+GNDc)%q5G2EdP4@`f?=irk}qBo*paqZ{V2*e5{)>UL`A?M_D)kEFZ zweIlRt3WgWF9-3_dGzRBE;?JooS{VLYM7Ib=mNl9ZB6|9)ikr}hEE}+H-fzldV5lj z{GRPwa2XxcuIZB9ZUhS);M6dqrErTyylH><=&a`tAFj)#vKohyFr#A2luOY)L9QoL zUW|5{k&Dr+`ju3+Js)j@sm*54O3|XS!N?={f{IBhf)&SgOylz-SnnWjLsKoGn82A( zEZC$W4DP`AqX}Go*@*>S9!;bXs9?n=;^pWx*DzsbQah0?(K=>_{=?Z6D#ziAP$!W4 z5P#gcMfWOaW#M(!yEygd${oG%N$3lUXi~n{B5X>RFP0lcZMdP-C{EE8z9J*Kssy-{ z=O%ZxweLK`XbUSi)K-VlmB+8hEW%f0w5lk13WiLpDgjmDx=Gd>*{L;y#T1rWzP&Xw z%!|I;mv=7OuCvfA|1V?t_1jF*v&tW#pA2xxyjf-Xdl?GvB+VPtX)Ijc=3EDTTnc`( zbPz$CK@9^8en6cSD8kr5zF*%c1wX z_uMAufL_|j!wqbfT4d%S?BJ>BSQl?#_0jJ=k$=w;&3}oCeuP$sv1!uzckrzWzean* zK#8qKUKA7)i0z($_dKI%)Ka|bhK8Dt{)m!FhQGn@z|ek!2KnbEWVc2BDMsqu6>Ua` z4##2$o`vL-aWpMC!|`{`D9x`0rMU?TR+Dc9z=kIi3p^P^WXfxkGJwEE zDV!O};U)(vGY@FVxmol|xWXL15D8XNy||Xtlh`@1vt!Vd8k^+9NnMhk+KJ|Yb1yj3 z8sL_DHbHyMa^n|vO2;@ZyB7s`XuU1BZs&xvHO%isy%3a_LB`%d1jHCp>KR5R0bY#l zRv|j!fX9D4C1Lcksi7Bqx?-kcMxhuiSDT~<@O`-7Lx`aqJ~E9uK(mFW4zrbz z%AWPQRBy-JdV6cL-gw{6>TLam4V^u2b3;bnvjycJIS6>ZB&^HpT>hB`jtz*on(bsA8+co-jqHh3`gEjhSjXsKw zS3j0cqQ0I)rzI+Bc2j?aK5%Z{3;&w{AH%s?r+*`@#+^cl+@dF z=neE!`tx<{swTUXXfioklbuAgTYtU|-C#pE=+Lut=;=BX<8bPkZzLQA{asG|$(Rif z{=$lsOEP*0+!}@!L5NByFN5^*J5U&SVo(vtK+)ct8F@mUF_ZBL>zISr4J&JP*-^qv zv<%y{40SETb}a+XlC4GztmWeyLwIp`8|N-Cu^iQ~-@&*ARwjw%AiAq}o?Hh8&$&YCgvpNX_sCsmPxq@`ZG6^S^4rT*LNw*}RMW_KMT(Co z2RL0^(wWYrsz-82x&l}l2Y%B5i;gM@P$7AkhJ~UDoUj)S4(O45Nhcx(LF=dDtiN3A zXCc;3$I(x}9P(?bWcjI>;6}|^f^j`c_PlZI$UG%;W zKOJMALgv%aLfD`!FF;eGoU88yF6&j#BkB7=#ipr8?`;0K>}ju+j+;4ypX*7MFT&g- zX|93dCa#R*<&fjhamj3|aOSoTFx3y@M+WI4Hb+f*^gnEc6Szf$)y_w`3!bwhVddm{ZkFgc3NJd-Lok6OCH%7$5@^f_N^+Oc= z;am0C=SXDe>-g)0PJ7|L?Y!|d~!P5BhExx`M-+~rje~WKni*HejZ*hxn z34D0{$fQ?Dw|awCZ>A}#-ip4-#o*pV@7h!+o09Pte+!Ll)F_*yqHKpWM=H*U^t0KK z*3OEwc1ENxHX>mWlR^}?6UE(OB-UdkBC<6q9#hv6Vuz&%EwX}ryZLZg46=NW^@hg# z5O_q!OnW4wC`Rjk))uD#pj!wgYLVuR7f74Dm^2BI#kxX-6(+&1*F4oUeYK_Ff@Mna zfQ6!sIZV(*9TC{XU8OxHvLQ3EyAVUt9WDU$biyM7 zCv?hdw5QY4X&X8deR(4~z&JYLIJ$Tm(S=T;uUFwP@}T&c+Mq5fyuhGLIW>h`)f(c} zsp00Rx6VnBsI!Qg>Avu+bOW7U^pRMkoqJ6`(3xn8x9d5_8|d_#;=gN8@uAL-#uDy5 z<Ow+ zfLIlbVQj7@R;e>dyjd0Mp4~8OrW|Ol8ISFKgx0EvHFM8d3%kmH&z(tY{k&_>40PG+ zR{8&LwRw@0ysb7cjbEZ^MB#}YU))@*60+HZSX*BE7+Zn_!AQ)Cgq164%t8Pou{(kR z)98f`4=O(koh{K8k&LjeZk*2+WIH7rOXmSQu8df_gld5{?x8kpo(n;g zM@Di2Ks)zZgaAN%#Ucb)VvcRV-A;~BIbC$+q{leGP)*;*XlP%qb2!=-uEJqYN+`WL zgFwj??Pe5Fn8Lnssospb5<5P^2@h(#oUjP9rhqkq`#~8?Pmf410EGRjMF;@I*DOMS zCh2!jdej2Cm`_~?r_M;oiI5^=66CdDc@B&Bm8O}WA-B+tbE_XmYY9JrpG5iH`(cIlpxj>Qz=Lvy&Y-OFII4wwf1UZ(=b=vrM@$!> zf0H4GLI2d5)jv_t|8++Hmr&=_hTVd@lv{95uD2(M!Hvi^J% zWlZ;5#{IT&zhm47jQgN*ziZs@8Tb3f{ef{GGVTwJ`y=E2*tkD2?oW;TuyG$T?oPT` zJ?#zO!-o6jcrNn|1n`VPauDw+vPP0pg?Et(a}NfG_hUsWny2q?dJeM2R*5xl2I@Tm z-kl4S^7XZ`_aSn=b_mZ~Q4hr9Sk|Q>@qHRky$~-q%7|Z}@zfLXIFhgF`!$|=BVI0> zk$$1ZQ;)>Ug)!n6X*~5xyxasMezC?=&&11JGUAtLJoQezToNO`kk$b#2nM{^3RDPm z0854ea)V4_WOM+FiUD$qOaj_<0LzX6a-B>9I&=UFlL2z2OaeM}085tvkH?AUbO4K+ z0bh*+j9yv(43MjK!pbO?6@meB8BGF=rlf{&9LKegdR(shf+V-w@EuqDs%cgXc~>%U zaX(oK-2Uq)3WHf}m*5{Amfg4QG7E|0&>UU@)0K;z@YMex(D; zV7XWocj}Oj#>nvxlL$ru%u>gI!&?O0PVn6VQn2w?lIk|`JMRapRjPR2)o{C2@$(2D zeuefApI_dlice#?U^2gI5;&Nm;fopKph6l=r zs^a1Y5r!0sPidOP$klKLC(E}z22Jz%t!@8lfyXWIa|7_XO)e)VqK~3B!xZTQTkooU z9^UO_P`j59In=&Eh%9Q~B}5*j5jVe5^_Oa&M9+Msc$PGK`i$o@iQK7fNF!c+W&rZx zY;^)BQsMWaW%!N0jlMjBCj3h#@tN<_!L^+qx5meP?#)9m&HoOdmteo-Q-2CIF`TYN?1 zlRSCy-RW1@Jef?;!Hc-F#lIxNFu|8EXXza8lR4H=eO%sUTYywwx^7qQqtEYU3V@bKmzxT_q`s@IE@;sG#?lD@#BVJ^03d#65dw^;t#svqc-NMpKw(ndBS}X?zL*LM^Z2lrXoq_kP?D>p#XZ2C~d$F+?$$p?6A3$#3+IcrxrgtHt$#J->PAg^8b^z9)|mp87oqu3G~A?3ML&?- z?Kcl?+e>volr}BOPxVowW>L~Gp8BlFJZJwgEsG?YXQJHE|E!kkUM&?)h4SFSmnmGM zPX3-XUCIKZT|Tn{n?{_>FVVj($LX(kV3xTBTd8~nX$9IbcIPzF=%9A}8c)mXsj9~4 zPeN4uZLcGL!;)u?q5=FEzdVr7XY-Apj~1fL`*A^$Nmi^VF&$q1=QGWEj`S-Sz#*I# z6wFhor_nB&#MzO2PMesgW-+y3sHT5JW2C7J|3m-^^!V{+1O1se=!Nq%u7;gQ{kT|i zGMDV-0c;TPT+ks1|5qtrg-;{wheE(T1-{=}1en>mFTs+4eS-~jdlT&Nn!VG8^MOd# zS+K9ujaaa)mcw(9|T{GElN?J-L@IG&l>| z5PpE+=nHhbL@4yb3N(pO!ld4Em5=#SZlZ?oF!%x9&}SW%Y?adl8`&~wd?+R+*@U!ZE!X5HO*--b{UD`a{bmnqUhwSmpoYoO$LL#8GHc|KcXvebL6mV+*(b2XKPL{SzI%M5tE>>r4Wj;nQ@s{sq6g=C`B1<9YwT5|ya` z8BXaBFxtW*r&)L}9Y^`>Wn}ael98_?4|11eL!0CL8}e-4sE~KS2JmFuKTwrx3z7FL zT|3BJvRK=|$$0%SjS6HA zhw-EPa-2Kj)9~5E$dBhvcDSP{MM%)&tE?}j-jh_*RD5OM-NejP&x@tQdAcmP`Nd%Cd=vZa5pLS!cMPZp0IkXkCwY5 z-{%v$t~}=u4l&cKcy#P+CAuF{F63YT1=W>q*om)70yaVyZhVkPTHjE!A$QO(!h+Kdl zob6^4)gNEsTTUBH!G0KJj-x5E{2q+`4e?q?`P7po{UAtXDLxo`a3&p}fwFWTBHcYO zGr<@>iPOrDPefTdCM`?!!PqNj((wf;OZO4deOdV7vlFfS_`sBYHrVY;Rl{ev;sp4N2vy`c?wJcM<8Y+r=(3om}J zHC+jOW5Wwa5gHySBs?FMz!NO+Y)G7J8(Oz%Xfopo#(s+Gs}d(m3rd`fqrB3D#_k2$ zlZO{E5ZaoGa%f=kd|f3I2lKmg;g1`C0Lf8 zB?5vw<6s**e!>jr7YiHW;6s_MUY(=zf%M?B$jn}OS%RNcX67J&Q@-(;+lEqR+mPds zvukNXD#O}t^o`(+4jPa`=DX95^kYL52^dMxWZ{0>`CAIdfF0QKyD== z55?*>TI++H*cxSf)e~fxadx!fTQJCDS9n>Ru8eis`bt-VjC`u$kKr1Y(v0EC=6;v-&*QVOc((?Vfu zw<=%hN{&1_y9`f&Y&VE(GR~mU;jz-f^M`R2u5jvT3T)@)al4`VbewQnw}e2KPX_ZT z_TTM(GNmVq96!-?ykQS{ZnCd9Hd^E`g_^aR#48{+FJN63koIE}z?uK#nhzNYa_*o1*7e6!Yd@%OH8Cd;#A1y{R zt$xR^JOe8)`i-&6YNnOv{l=bwjLle@;$ZBTGqCdT-`MwNVC5CSvCq%I%9DU&vcJ(} zYUO>vF>W4dOo}fq8wgd>4aOLOk7AL@IYWlx{ z;G+v`QJuAq&m<6A`x?x1Z;0~_`eUyDFqG?rl0H(>o%uY&;h>10XF$4#s_* zI>SE=vtxuAePl+r<#h(cJd80h7m%1(Z=*jv9zU2HDCbMiBkt*C749eqReqRS%|+ip z49v>A{pc+yit24pVf>n^AH5YgS&9^L?a#@2q)e-$zr(mxu;9TJ#Hyb|+}}XQe2Fy~ zaq%YpOdb&q5HmiDNFlmgK8%QG=Jo2cMPvU?PP5C$BxOyKt6f|VI)RzP>K9#1q*zjz zi?K*bf$ZHsLy&e>zs>h1gN! z^q`3jF_4bF8-i}_G$)PiY^*VO&Yi%&t&u(!_E7t3-$Y}@roh-M5vsyXV1^I0QTnuO z2WVJEL28=03EzOhMh{jXBokyZ*aY)ps$^Y)v+zlrL9pfBQyA1vwCLKmkm!}T>yq_r zZ>Kr&0vv{?BkXUS#do{@%!z( zzi9QX^NVFR5v|#LYqP$l(5t40ki*!B(<4S)fX+g|hkKT)=>vd&Pyz_iv?Dc5DF6_d z@@Rwru=OKA(AHl9O=usDF0m~ZoQc5jUJ9yalRau;zf@w=$JlgROg7TNyVLSPzn2T; z3R0dv1MAiWJ&Snd+{A*OMZ9WmVnNR$rchk>Zj<}OC>9JJhJn-9ysga9m5`a2NhJuD z)qmLBvClYN3-w*n5moyR3b-yDae7+=@4{DE5f^X$O;dm&Yz0v2AsomgVe|l^=zAN` z?Zu#sdr0`NpkGtSUL)SU*6$~x?l_xRf5NLk68P?DTM4h<`_Z)+spGpUU^?hl@N%E_ zf0q>xMKAHK%l%qGV4OqOJR3Q|P1c(%N`LK-8?U-1>FR?k9pB;6bkwG?5u!Z#p?JkmwF2aDL|ZwiM;hogUdHR;xy>;5b! zz3aoS^F{`72Q?SoVnDooE?`xMLKkcwFp_K)u#O@XRJ8_Py#~y_5$iMA+I^;P{D$ru zSA#Djtb9#{Eatan4H)I zU0$`XVZqGyj%`p_39;b-+E>I_85{ZXmhWxUfz*20m%**> zlr&zPuuUqpl}1QyWjj?{*=nJO6VbKc>vPh$*A(_s)8oL$=9nI_x&m|-vZbJpuZV%DN40 z6@y-koA^1Tl3WpUxa5j)^ZnqZN1&+e>&TLJK>+z2dWuW1p!-@(CupB&45(oV0uAOZ zIH&-%6N#4BY1}U3&Z7&HfW!`AVl*FOBY;?IQx^b;!!1I9S@HpKgryJw3O@=6C_Ma@ zsOU%r^ZqZ-2l1bpJ{TzTgh!phSb$+1+I9(=+qRR(L7GJi4@s}0cJ705-nWtD9$1>a z#*9-a>HY$P_Ejo;0I|%D?&i6~--6>`3g%C%N1mJ$^%$E<7&cWCE;G>_Kps4wV8hmF{>AMmFo}m? z@#v^Fm@AoaFg-JZXLU4^a2y-crbcXdX2dQw2g5TXcx`hqJUZCq3goV{5p5d};{M|q zS9`VY;^av|^_^leD^Wn%#z!`1BGp_-m&gXcuBPlbT{Go%ip{~rnZc>%?7%Z4x9GGR zx`IasJG4{EIS}scv{QV9Xqg{<1LfPE;l-0ck& zhanii(3tRTcI@xELLO@oDh!_!3_C*9mKtx40NdQ@m2*G{BM-&yq_xqi5 ztGc?UyNEo``+V;oZ=k2{J?GqW&)v`6&RqxIxpd(uyLhxMbj3sUD>gi2Kizh}gIMsm zaiZ6t%vO32w$kqfD(9}lhB{U>tz=)qcA|)4ToWu;rQ;Cg2ENH;M?Q?WzQS~fDHNP( zng*?lXPOSg8KmiOyNj`PkFCUK;MQ|I+tP&0v-INJInOk-CdMahY~s!30R}i=Uc*1p z^NboIn0AM}O=Zh%5~L4fx#{0whu*w@=^YGwa^$%+8ONK&Nm&1Y@vfu`V^y$toBv)H?^ig-(a zD>v>z$UED;7J(gFz%GjEc-k*9%hrl>uR<|#++m^l${;&-qYUEUMhZMI#L4jU$0{!r zA&xXW62t)zS|EoNT;Y`d)M}~f-Z_Nr2QyaCzeo2nO`UfM4mZ{<(|0IxRqo*>G0IJf>MrC}EDkHgK zN##3qvuB6?&w?o|SOKnRUG#{$A`V}bfMdQjvJ^KCIqU?`o4?6XDoj}aP{?<9ci4E26xXILZq z9LnC9Wj)y;@+n|Ik#`l5UHBk!NLwWZWM;7UD>K6ynRlbijak-{nRSI6*sXOUxId}H zWMe1zD<|7OPAVO5E9*&ngCww?G%V{$&!Gp<^(6KG6n=Jf!omBp%M$=*0qp69KggL2 zq0;YRoFO*2%ETVqoBtWNzn~3I*5E-!V)84Z8y)roQbigmzkJ!`DP|Y zH)di2GBlitiNDW`6PSwOe$?C8<>PGZSg@w9vW(wZK8Yqr2bRf8j4>UuSwDG+F{WdD z#+Z)r8Dl!e$GKUBCnskxm?Jpz9t$T^w~U z;L#Vnr!)mM4L5?EbBJQy{}6b81NBj;MqW3Pm8xiB( zRSr9mJeh&HODYMLFl>#tP33?FOn$YbBMR)kw!U!GRdw1dN!*PJCqW? zB12Ttd=q3SJqCrCdyzj;oq7l~qEka1B)zKCPm4;;zec5|(^IJ*2ZySyNUDJWl^SV| zMrzkv8IR>;A#O#dW_)J7m7Q7DdMmX*h9{ zdj-_yVsm`tOlZBO7udscKa|>5qVEh1Wm2p)N+Nep7loa5B!jqaVO*$xS3v4sR7}EY zA1nx$`BdDwlI4NR4w6h2k1~0gdO3u0=aYG3n$MHwAkr}ZEXLIe{Kh%8;^aVcAxprZIA|DWKT$I)45krflAm`+Jt&!6pkH54{kBs8tXvvQ= z%eSX{aB-mRp5Xz+0lt%KZw2P#XUdeJc}O+3%`7ugKL;vOYCsI4z^s-^f0#GZ1ccO zr0hboFT9ns_BSVwBt4;YW-Tm^9}eI28#zka|xuJm-Q z%gZ7E9n$tC$iWD7J4>SiDCN+py0$ zF%)}YDE84%?4OBkm>XS_!>~IL+fe4+La`@=VpoP@&k4m|9*Vs=6#GCZ_K8sJ-$Jp; zDfRP8&yFNGq2l<%u_-YAOhKr?_#@cL2vZMR83*cNE91aG9nO_;pdPj|4%EX|#({d+ zN>5)8Tj_D@$z17i>tQQBZar+J$E}C0^tkn~l^(Yqw$kI)!&Z9Sde};jTMt|5aqD3# zJ#Ia0rN^y@t@OC{u$3OS9=6is*27kM+2d2}D?M&KY^BGohpqIu^{|y5 zw;s0Ab|@5kNhtRAQ0x<-*uR8gr)?FMqD7(D)uGtWhhiTk zR&<1VrN1c@+cGsQ>_Nne22qdmiJ@V!z!4(VW1(1U>#(qMiEUV)PYuQ19g5vVY{T?+ zZxe<+j@X8!fM+s7IK#G3NZ3DyV&gqwVdsTnj|;_qDir%tD0WJ3SbAp=+pq+W3&ma% zihYdOhNWO_8y4@hQ0yvV8*;ur6#GIb)^fr)&k4m25!)~)*M(xA4#hTa7bevXq1a`_ zHk9f-VjH%y8$+>=g<^kAY{N2w@tP29H?a+6UJ#01Lu|toJr#>bIx-Y{Whi!ID0a#~SiC0?+pzR62*qv) z#dd8U#(4p;4a@hQQ0#_KY|9Q|oacmM&m^{CeZD3XyCIZwe8(`(Gl*?i3a1j=Ft6?m z#cmGeyvww(c+UvM-W`hlb11erJuKeih;5kOTSLSCJ`~$KBP`w(#5Rog=FqVJ48_i$ z8OHeq4;`Lb30JVh84i$vi(4yOP*C;~@70Y}~Pl zMDFCC`C%|qY{DM4_6b4^!7bw7jgLN%Q$ca^F^|C5OuysgzbLG0J)!SfKi%G z2T$_mnIGjXd)na7aLT_1PhGwWKg*IZwnhaXDU6R{hrs(T$c^|cmv2_l+VRfV zxwxNrc=WLN2yyNPkag$*j7OLOJlAw2O@kqE%a((-Cd7I2o7m0TJUw$-C@9Cj6jUQeqMjU2 zfb_lO_?@x~%JHg#f^xj2pr9O0jM37YQ7{8l)chDzBL`1%@}%Zdz9=Z0sGR%zChE%g zNUjaoc7%!*JI6=V(B%1OQ1KD|jfss(wbpSsh~gvX0$Ry#kNwjX_V1hj5h46H{15Oy zqz?c2nmsMCmbmk5RUEO%F{rq0}`lBt;BJ6lo@+rn6k(I0hdW`z{QSz=O z<{VllcHH}Pe(J~C3d?alfjDi6a(tw%4fhD^1@|)l1)l}-J{IW>@Gxa7Le!T*S}tq8 zX}Rp5qjGmo(@v}ItR7&|;Y_KM2w}h`>@i^YslE(>@J_x?aP7(1AvFk(-p0i5Os*4N zqGyZof{Xl>_&d-^c;P423D5LeGS+E&dp5Vjkf((U6(E*=oa0~(ZnqHJ?!<{y>|%^m zA$dBrj!+VuJ*AhZ#(BDRo#2|M?D!jm59H}qb;3*Z?3ldZA$i(eCw#*^oeSbddAbL2 z5;Hs9_s+N8IuT27jvb!hjq`2WI>9yHI1n}nAIP_!I^iXHcGX_+kbK*wPWXoTHV?#& z@@=mW+@2x0y@?ZvIikIHe)oaCw)>ah9GJZXH_q=uo#2Ofy|1VlK_2t{HfKlCdCK_o zzWQEgyE^fT*c?B-C_~E0sT00oIqd`DM&-0$2yWjH-2TLg#MCj~yPS3aRo!x;s^lfO zaXIZ+C%Co?>U<5t2inEDpdS&R?3}$NszD{*Hmy4VsN~t8JMKIB!Fqb^g~RQGV*avCNkQJwEMJd4u>54 zr;{4Y2JMy9Xl2BPG@JklkPe4KjuBCSRCFEor#S1+j2XHC^HTR`m{;_IJ?e@rbEyFT z7Q;U(8i^MHp^PBva7^`LAe1pg9gY}vqTu-7NPcQ89(yKA*l5&v>816>&}Sr~!mAh0XVsN8$WzZn{x|cLH9b6EsS!2ESKR)I zEPdbk`f0fmCs`^|cv-pv@P=7>GLZj4mXgga>p)T@2QofWYynLw<)G1%o-w94X#n=ZAQ$_hWc~jR?g#g#7t_v)gS?Y@e93Aw< zfgXDxVTKFsA7c@8%#nNFXI;@q(nv=U0*z~udq#F9+R0m|FwuwAw>WLD?7a^vX(Rb2TVV=cOL`1uoxy}TlVF@;poDECEu!$N7+C?0u z^MK0W8 zAdPLaAKT!`wS<-=9+&u%OJ*Nh2@>7I(|QucR#b%#uoit533OPC3V5F<0AlShQRVz_ zgH2&kZ9+9QLAv}P?>nk1$t_r$20v8`NzTR{WYjO%ZMRxs(n*S;=tr%9Y{tEurTTiM zsPfyHES@%+Bl`e*MVt&C9WudCg1o7G*|CUeY<6t6W%n~j*UW!aq~1kyvb{CyNj*N- z+FIBVQn#wqZP_e%WwTjdIIoFj+eEl*EAXw^*6jFcKQ>bAX3FPMM3kI;A^zGc_hpf^ zXWQNVnX9>Mt~l1p=G>1Wd7@GDpQwRBaS$l*0^I_X8(yGWR9k%Q?tXsp6{aPbIqWRZ zCbYbl`2h-88Evyy9*C&jMF^wIq_BcnnMoOo_MGqjH+4BqY{SQT&PMBu^`4G=-}@Y& zZ82cx8JMGvETVV{9c`gCROrNd=9cTowWbol zEU`=j3QB5bMEUKFccbmR3W2zH7sZIH6$fT-8OC;BX1$VmWuX~;7ro9P3$1TiC0sQE z>EZkkuiQnx)2RGx_bo7y%hpWY>`Lm214!bo1Ibvil}Yu+7n5EUt4^XHuw~z|i02!m zha2*1yq6MgOFGlAA?dz>T5Y(4XhiD?;!{UpJg{6Mz*W)P8{=Y^pDZix-JV_Dy{q)Rq!G=4aqkm}W(%=a$DGG|1d z(Cfk1`*j!?zsdXu7w(O5GlG%D|M{vwRZ%6imWheSxYp0hO%1ez>lIdH5WMbZeetHY zNitYS;pCqlMBP+eM*QoYfNAZxI2cJhG;U0xKm{}|^EM(DF zY%p51vrsJuY%uzv@e)o|+_I9aao3e!b~gOblz$JDO*Ic$RaPDsA#Oa3V&3TtE>OV? zrp)_c|0UX%E-X$+E@8k(=&BJjDqYw&p*A*CiIqp8WYUFMiDC31c$OC3FlFiKSKBvY zWwFqi82&F}TM{ez)%Pt6RvTVa^H&FG3PaQq_@F1FpNs)Czf7Uab~(XsPWGp4cOhKqLUVFllNujAZrU$k`9|$h+Oqy9nnVpq z)}yPbWEr{|v_DH&^X#kW10V9_39c!Lkq!>I8Kgw#kUDD|lae_j=Z(R}^`E$N2+N-r zhP>!!OD3!q=fy?1N9(!i|KI*KiktAlUR4vm*;2D$-b2?~sr;o2TdTGzDr&Qp?6=zr z@uVZmwo1k|I;`21Q0ak>&O9|0^%82U}X^ z|3P`psV*<}fnnvPrR)DUhKdM#Yl5JnzUIAGZo-3Il!PvQH6(74AK zL)Sdcm9er>hx?T2!rp4R+C;yTTC9fVQNVCZ0jR1Z#q@Lu@AC1npcqbr#jsx1h7PMY z3l_Cfc?!sbG(&CVI|5(+vtlyJ#2i}rlO+Jv%Jbc-{@O(thoYKpbY7#g@8zrJI0B`x zsIL@r{%xUe`ytXFtGfvhbGQ8;iZ146!Kt{&NNtutVs0OxnMC)aNaC!xd^lU%di2^P z%n+95b8g7B8J&#o^rGQrmOhNn(Hya2@(Ob<=N!fYgSUvd@ER?SOc7$1AiZ@hqfdPl zg8WG9L#{7Y?1_$Afzip(0e;4IjI(lSTUMVadcoF*na9x3`{>Xqkso1Ex}{%Ywpw0;x`WN{ zi}9fePT^{n`V<8j*$;3VmP$T4Y zkp#=f8lN}-fMI+Ehy%v>J`Vtm@tuNns1LSE&m`0;{SF1+=CreSquJ)G4e5}O(nm{s z{MOp-`#2~9cKcdsx9?QK0(Se>8g8EuZuF5Gzg2eo5YlDH-$NF@4tap)h|c2NzXQcg zV)E1sU#$UQ-;!|9(INJGnMq>5S8~f32&5YZ0tEyEf#QXMK=GqA`CUIj?DxJy`@IvE zIOkD@KFhfPUs?_{PFn0WPMYf-Fk$nPmf*FG?$;R@ZVf%Bf+PpDR*6S;uXxHs zcD_(U0n$Rq|b<2?w!O=jkc*H zF1FiepzS;+eFH0k`v>H``$zo2^f&JQiM2wgHqyk94f$jQnCY<0=(Uw=;MM)>Wsv^~$Y1)&HW+wd zgZ4SxMnH4<0w~ljBdP|;)h^>ti9M5lja^1MJ-duggGH4QS~j2>yNp!HuVsAZT?fX4 zM;0M?#V#YGGd5_Msm_hSdT)NQ6;&)VvOkPP_5jZ(r3`$jjm)2gB~jYZ{v{sqvT$!A zelu8={*1CHy@@|~B9z$#5w#}(AKC^+Y4fcU+=F0op$1^l@<#}W!}2U$F-xmsUX*V= zN3c@~o3N7k(K3ZchO-yM5JiDJyO6}-zQ}FHF60ks7gCBRKJp{FrHniqkw5lrM6%2* z+&NiItFO$D_{d{GqPK@tRJyd=kX+I?$fN@_X<_~3KN#Ug3XG-JkHD$bQf!FJN<7`$^lducGKjKuKU}B1v>qk47cwHH~Pqp->Oa@ z&H3Y8vkNrq?;->mv%@c{jEDz$Zy^hh9Q9J)VEGB#&U({Uae#?!^=43NTSd+=n6}jy zq^$==P2y}e2`S$-mctRi)bG10 zP8OY3TD}=|8v^#Ht&!XD#Z@R6%BMcn1=)_HZ2jmfHn(4ahJd#2?vh4%lpY5I6oWQ; zoMSDM2M*7W1?oYY`fbl|@KN=9rObXM9&r&5|EUgT7mv4}-X8B+Ip?Ehxqrc*{@l>^ z7z1qgzu=J}qZEhh44m+}s27wyxu>u40<3fY3RL+!lxBo~5qP9t;J$B1`_YG!q&QW- z9T&k`AGq;e&x7Ey&V37iS5lZWkS1k^OJlGTZ1=4y?T7ckSKp8DPh)k-5^+oW6ZT!e zU{D#`WDmgQw?}%UJsQWzg8-=ZMi;fWhddH-0PsBzAP&AkKvL0RB#hBv=!Om>AVb49 z2*eK^g5UMDa%^lF9}Ov?swf=l3En372h|g3C-aH&f=ogXQBH)xB<~dh<1_CL7$096 zfXXAj{0^e>{6tiq9Sl?GEFO|vk)NJdUVIDsyMgz}yCXZmPCq)0?sw71ZS|^{`xq7q zBU5x=!c-yMyna##+AUBof_(6a1IZZ=)6MCUM*X%#O|M2>iHNZ72&Ujw5h>KF3&oyT z9olGJXiT7TYWh@^jI)x(gSNso&7JXG&-N+RCTgA}rx9^h7MktHrOcQ!j39A$CKfQ{ z1p@_4bdNyQ?t9qAlESKoLGI!(SbAK<8d^Ipa>_*aVfb)dIcLdkT-QPuuh^&itiJiH zZh<4eXdu1nZE+wa9e%I9Psc^KNDIn$e{nUr67va)44c^eqMx8_=-^nvp?R^iSn#Y|3oA z{|1+fu13e1aVDVUHG7nG1y=H8qI8bNwHbkNS;Bo6-(XuD-U~`eYRyTd<3QIMO|`~z zt%*V*8_y=zwkEOm##4G$Hd&KbZp4bDaqe^h&!uMLYfhT>Bv!uswRWx(ES0|blaO5= zX|$2E&QEHD+3pX)M<%*R9mO*cxsG!g7zc0$^$(;fgQE?fr#LuO$$r*d z&0`3Desaz7d5r^uwNro$=46Z5PLLpRInyy6il@5dM1(o$Le?G_@>*Uj+%ri!R};Iz z=1D(^Ti?=~38MNxW#d1~X59KBr)RoB4^lZh;-pk@(2`S3 zdX~wVj2^w%qqZQ-0j|F@uJLL!!W@C(Q=XV+h#s>ValF8C8{vSYyFL)-YzU8qBVGb3 z#!IcJ-e+hYBk4F{qxocx<}m-U^d@zZC369&o$CSQSv4lncAHSI-9I3qQchR_#v(K? z;Cum8j)do2gu}{D1%IEf}#Ee*&``2@~4miRyuFO94PWqrGCai=qB(dt^DPn4)SVw z!u<#aby+NpyQd;&RQW3&Zs=Gm1grVxCc(pyB$ULAGwKOYNfyZ7nCyt5A`XK+$|Rax zgU4o?E$7@o8EE7Z#{C=OG-Y7wI0X}2+%3VGbhbfG#obT9Z8iek)5IOjpAvM9xI32W z3i=qTKi48cD3TK~x4s=^i9ta1kf?*{Y@s+YreB-`Z;A4mC5{paRbKpVw-$pvplMMz zg=}dqv?tv(9AmJDN#!VBT7z1UPU^bo>l@R_A|Cyfb#0`c?8w&Ft9v&K+ox^@5^%aBmQixfmzm~yR!+wk>_mNq z09g?g7xG~u>h?vbFRys{VL8$P?~5AN3~t(3WU@B}gi zhRc^Go8X+M3A5Blq_3%c_on!Kc-JW}dHLA1%RrM%BAMR+ANHQ?2ci+47XKN_6^O3^ zQQ9Yps$~z)rb3Y@cExeYG>bHwGPnUGt^<-nu1Xi7J}Ge99nT`mjVVk`rq;G%?P@zv zqvi$~P)kU4EEtnYLPL1Lc3a`cm~XpnaI87WEg%SI4z}A4fbHhwFBI`~)N7*B_a6({ zH$rx9zjg|ZaEitAa?e zK2T(V{!r)X(-AMu0APG$MbYz<@#55YrCYY$2}rwi-hmFM`~lW4WkRPH*bvf+RbPd zNp~W=Nq3U`oh*N)?XzPpZH2rw?Q>LvNhL~_*VwhEyaGWgjipc9$yt%M%OWwh6Y6Ng z$d6yJOr=0%wrehHV+&YDdUL$=R`5y}=IXg&obgDbGr2=xMLnyFBc?o38p|&m%8of9Pdmls#OmW%&f$s-2GYom`0a}yyX_Kwd@BA+ z_A}5fpj@v>4-V8oxoDFf?5u%um6O6_o2b=|L{E1^IGaBt3Xy4U|hh z>A@pvpj`J!5AH&!Ip8hzol8FH!Jb+$S9{Wf&}Ts{DO%Hfe z0hjeDIaF4<3-dlK5F1c8cwi-~Gn;U?qBEI|VHwLIH}?)u0zQR@RDQm(Ih!bA6-%dM zP+-;e3JIm?v$4{0%B+VttH*Wf8dl`KXwSwdI!zpGME8FS06~$gD zp!>iV9SB$?p8jAp4fMB_0=kzph2s-XFC9%FpzjL7UWum{k0ub%cZ6W;#M4Vg6A0*j zA*fZ1Hr7?g+{x6eZF%HcimNo003A4K^agfM>!@)E!GJQYu^r5f3&9}BfQcBtzmM;4 zn3oq~Rb-+Sm5-i$m5RX$j{$Nv$uSH-3ClZJ6cBV2mxqlvr1-nr(E0T?qxLt1>-kol^mk;mmy;OXP&iBjkJF z<^Bke0JFY-N(;B$moVulZdc8j6A|ysKi4D!vjKFZoWSYXJf`zcm2}EF7Sg!~&!vc{ z@14%G-aDOLU{{^aNq|(RlSg6dr<3b4AAp?vSGQBsJhk1o$>KqXvy!V?r77N$6|?c5 zeuV#mE0~QkXSoEt|Lo^OXt}O_@AN*3lB%RPlR*5B0aQsaA>0FTegI}tunF;_%f^90 zfA~ybY>UdI;7!Ul%mEWsYEOUNeaBRt(of0$PNUL~%P@+@Av-EO(H*Fg2|3#ks4TXe zPsEHcpOnK&a*UgH8Y){p(=l0W!+&$~DQo0uYE&m9Wju&Lj~vnwM|vV*>phq_aP(sX z06Zf1ByvK1Jk&;L?Gd>rm{;BrIdI#}a@G?RI3f3pVYWe-(MM+d)}D|%rRId(vk1Wx za{OY$U!;S$&yfYrGYC2A2+Ly`zJEb^l%Cm!E!-+&a>+At&w^IZ$RQ&bP|wJ{BxmIK z*PM}~(>o*gELc>XXJx|QlM&d41A+5Zc&n}kNo z42aVVEFmOYSjWm_;hhd40Zxak@y^JdR5>FjnKUO43joi9$S-z!q}avLk{{1D}#0AXNFr8 zaBGAX5Kdr5c%-Nc{xF_P>kMWta)r`k-{!&#J8J$#y@s^&d zfq}=_I4h)?>sLqVcRvn(Z%KV*&LGVUj1Q|0GJ@>AibxD9;w|Qw(nCC^#4NGmBj2N2 zvdKHL^e_)7y@j*AnwL1T^nF5B@W_%>C&@oM0u@-#3t4UpaJKumJk3a((?Cuq%jF0w zh>R-GgwJ{5ZRgUaQ6X{GOu*G)vVNGFhz!s1`2LM~g7e9x36Oxte!3{h3SIqw$#5uP^rc5G_C4MgLx8ujz_0{I*xp^*#17ITG zxAPhFv__Z$ z?A>j`+znPwD7f~VO zseUr+gIg0Y^p(mcN{2~&=Sh4_qDg4dd{Z#*wGqk09=@hJC8<_VsT21aYto-K0W+{9 zmd<8Mi^;!$J-2#g(Au2HHkXbAN$Z%6`9x_7i8r?Onqqnk#Z;OqFjSrFn7%z&+08$k z?1}K#27f2k|4%T1u0bHD&( zj#&!GZH{sqcPhwPGqH!8hb?#wD0G`)zZKr%Rzoo4hKno;V--z+2HJiDO=yr z;9gY(db0)*|JI9G902^z1Be5X5Yeu0XTV3Oc@Cr3YHxE`wYitcL zJ)m%uKK@|j{cDn6gdy9f4lSq_P~*LDDOQ`}&eoW1_l-^?*$RcG5yJlg5Xon80Dy%` z1&9MDcZtYv_LD=fpUpXEXQl>YS$?y_cGrhQOz{#yQY1n+-#J=bq9wyM4k-o|9Af+t zNRdk%0Q|`Vhy#E>djN3&@TLb42P9|EfaDBlBuB9@RK>?2#&?Y6?9VEKlRt#&!PQ(} z9Wc-JWAxwL-rnx86W~A|bVmd5Xw1;8weo#l22ns{5CuaIPKh|gi}>6lIvQope?zCD zq$irQ^np6Nt_S-zdh%!Lg)RGtScG~8 z|AnTXTqNn_BdI2*bv;NhS*`Qa`giywt>Qop;MbHnnq#}ePPFd^jMnTh?EHj*c01oS zFb;PN2Qou9q6o|-iD+3koIARC5iU8b%?tY9zW{MgA!E<8f*#}}F=#l>N8r~!%PtFy z$Ap)Y2uq~-&SlZ1^?XxtdjH*bah4wHq;TxA@}0+RpjTq^BJjS`#zt)C3=>trWA_C- z(V)lb+pQADqJ#lI@~uhF^iiFVF+mc9e%#hHZ^I4+9qc=#c~+pDwoy&w9zv-XuyTri+xZk&SaCa58lZ!MYBRX>^+BAb2JL%LI|gPw=aEYMM>*c>K8hcQ$?(N8dE~c<|5pHhtT`w~9RP zr!P13EV4N6a)Deg1HFqu_zaUd+aYsuyKdP0?z_l8Th{3RiTuMF49-_dn6x#LK_Cq&t^BiwnN3BWgB)i56s=CUm@!3b0$i_(s|zk> zMbVlF7qg*gO@fPAP_!n)#pD;QDR43AMLUl}h+;t=PTa51(~fpN11D1qXtNyjVO==Z zlf`zIVVTHT4Vkefb0pmtse2LKcd2_Z-QR)RcF%$n1B5MSPn^#v9;d3}&ZpJ=F}lx% zn{%{@i0cS^|0Vg>glyXz$2`-89pY{g8RH%SU#8HKaQoqd?IaPztd;*ZxD0G=3qM+Q zTNoM(bUG_CA<{Kn*MqG{CbG-E-rBI*XM>J|b2~OAxiG7jXP~IvhNGyaudINuYG%wfD9& zk**%id$yOoz>EKE+RS(deZKAPh-@yhYZ7mVI}Lt_{S3u0Rq!RcFV-ZyLigw3mV4!t z4b2nnkM(Fef{JW2;Gk@mY`JV!WDx~xk~yaHC&t1I9NXl)3AeN%Q)H6U9{efPXH#$# zu+P9@oWezM6MtbW3IS>fJk@tjIO=(s7MR+Zpr6 z^5dH6<EuYf#pyg*ld0+4Ea}|YODA__m`;9c z(Xh&3O3TtcY}lNoXW!%r{S<* z8s3#O?5=6BM#S6U&Q&?(?!GAxxsh+&au5Gnl~buPA(bx|ul8&!qcSb<2L6^di)UxaOE=)(HDm4f`RwZ&3G#>HZoinRics4{Yn2_P000PY+%XSlrzQ?o47f zwcfoNS{nqb$==4`ZM0QSDS28xB#v(bEN;ofo3adGu%^W*v@f7{^)itz(m z!)#Jw z=lNc}+h2K#=v!3pboOJryR&3u0I!UXEk1u}09vo13QK#fWI=zpZsU!`xAKk?`J-mt zPB&WybU?2SjrHR?K=KED!+|C(H~ZQB9&ven!%-f8#Pt!nO^rU5?prio*5f(IL#{2V zzA+hybUZ(KARf;b2U@XKwekhj4z~cfjJAZlOvrp6Qjve0FaJTF{0AHPZv*)jmBk5` zJ@OGWdo$9FoanhlSJrocA+nA!I6jE};Sj_jV=1?@i@mz7?fw&|ZHp5E9jLBbwDovt+s34g zecu+R?JiB4rY$OIJHktwZiV|}j$k|74`^~u;fZil5W{q@MYJfOPta|Wb2{Co8y}(j zn@Urr+jQx)q%288*fn&2UTH3&`wn$qNVl1>UP?E!uA=|p+Mj6`M?yByDUO1JI>kL8 z+@iej+WuqM@$-_!eR&fV)^Th()a=)TnB>)bFRy4!I#6i^+ul^q6}6 zNt-V}&n=1kJhVvp*$Zuv{FLvxX8D_lWzj{Faq)kw!>Wr^x(w^W*!>C zpe4t-mm-r zX+r;Vj90D?R+p<_JKPTfjrQ~qklsM{L%OTmP0p`(i$E{+?pQcj@3@Gz#r2Md{rOMo zT~g#*?8}E)OrYKgw!>Yb@_n6C9McUxPB-&F*I>V_EOFXtDQL@FR$((!as+q6n2(8*!MDt&}b_RCi3L^|20S*4TcWRtZ@%jsn6 zuu518N|%|TR_SCqDZ5qr7@b@K)Ez*U(L}5PQhn<{ep+0hbyt8WT}Z@T7mi+&k#9EA zm0G8$u7qPFK$QSA_Tr2Ek{)pVT zvdtr3nBdDL?M-k)kHym+RNirG8>`t=V5dTXeHQF-(`#f57;|34FOF%Yc}v9@9pKp| zu!~F({v_z<1<2vq)~8&W#tG zci(ZV&F6|6L%bmGUWFAxJOkjvlP01Hq#GI{S+!-Hwn_C30q0y=&s27q0 zxERN;UKfW^gLIDjBQ%G=?J~{P}DC9qJ318*r%DubO?83H7b8A*0lQM{(gOYyN|bIN>mj-9d~ z^GE|1Mm1iY-&KW6pcCam?qHZ)pTkqw8YUr|9Fr<_u`Oc^2u#ua1nD;AHC8#;;hDj} zy)tGT>gi-mA^!rWHaRhvluU4&@EI(3VEc>G{Uu#G&-wuy(eREycYKn%~eQ_?=VG-++V-s@P%6mW#A0 zlXHioe&a-=AMalwsrI-2+5->Q>w(N-*2Mc3;+0f-k;kQWdhtxIibr1sk#{G5f!d4M z&efWtch00Wj-WZY+v1Pq{tP;nmWv(P8g1f3nB!Q^TaXKN2zSPZ(qnOC!E*k|wRj$4 zDD8(;#53^r5ou(bMfPwQcA13zdnN4R(6G!TKkSDk>^~}Dudf>xZL9Ve z<;BLbbnV!J>cxd?=$c=R@1!^bX{481fI~ao?SLkhtQ6x+U2aEi)jpi^D2%TUf`+WMT5& zJDo>5oX)#=DlLh7r|>@NpTc8-(3t3QKw|DmJccJZ575a3nFjt2gmzaFD+MwOb`o|( zNPn28NBUNAu)mgJ-Z(nxMVZX-sElJ|CID(2kS;J7ndL+1VxXNAW0%DNz@P^Z2h@Vu z08lN+qkgE5#-0haN*_mu(&kLTXe-8^=NQthgp@v#@@ti_T?W#;N4m2i-C9WLqkh0| z31{#?iIp&Ax~oz;y+XMQDP^t2L7DNNH8Kq__j0VZp>T+|I?4>R5$9P1bp=qKjp0mW z+{8#T!*PDK6CL;$w_fz)9EJw*sxwwwRf0jFd&~lnU(g3YBkjrh#HdjwlZ;fwq zWL&@IGx7xW6S*-LvaurVAh|&wi=5vo{Svi$H_Mq0qM$yxhY37G0@KI9{8s6ch-RLl zoF$a>k&@q1N$Oy?)GSo>J&EIqJ8>XVE*krKE&NOxd`p91QYtfU@tPD z{>s_wQW+z52T3|^PjBjr7stT%42D$89Y&B|8zZ&xp9;Sp-s3|-gy8|o*bK^CW)T&w zT2V7?KcKn+vb5`w5d&=z)jAZ?r;SxuWi#R>5vl z!QSIZuR}d`3hiY?N3fg)B*o*9st!SWWIt2UDW%szmP}{XVq6L8h778@qv&g+bjJ$5 zzHV8;*Vio#zE-y+I8?Xv`5N7FvE?j57zb&IJNw}ej>hx&(2zB<50bSChDV}H*b+$y0wFATSepQrfQ)5|G->GF-WSGo~MK!%3a zUd7Kq955u1j2tEq-3TNgL&HGgM+d>c>v26`H>>+?Y}oULzzIkoD!TJAerNe4%#4bU z7Y8an-W;g-CRF*1xmXj?#3~$($IdY^@n}dNPt&bFo}T*nCLGR%-QT9}dL&p6tFVBlmPIs(ho=^gO`=M8cxv(8K|DN) z_cA*0I6!sMY5umt7oJ+abAxzzYVoUscz6_#+QM2>^C-fJrj{6#sU^l|YKifgT4H>r zmWVI#{=rE>euVRa3QM5N5fmzg1gWYnf0JnXmRyERVaxNr{S zcn!x<@G_0vjOu@2R69#w|~kIB+9WK_Vm^YP3#16ISe&}}=NAUKfA zL2w{d0>;2W-c=b~g$U-4AXeZ_420lwqtr-OZI3?=t^f7qa1W#!w?hE8kLBEpaQ!fi zbsxnSyJk`Me!Aru3vTq|8H*h-5B8t2_%Nr;e8xf?an&nv`YF;a^M5L??&%oiTs@6| zcPw}sLCMXsUHCf|$hWBburC9*^B}|epbYZ?6B)!2kYOv4fqJ%k24&FQM99G1#5R}Y zTOSw~vUj2=3}_cCw0krS&|bF}LFrR6*dCU^pa zDt}qDf7R*BqTX9=B^XeSmpN=#k6b(>jn|icLJ>IAta8UH@R*^IHSPI`ng2|zIy=F9 zjM_m%8DcULadyiA!fGrM)C-KtQRw2F`|W{vrb=G#3!uzY74M$g4SfTr1b?{|QqDIn zHX;$F5oAE=O#H#cAm#(6r8)ruAM%@r@7C4)FrQvyjrmB{rWq;ngjv-F~w@BcyP`=g}15Zudul<7YIn(02B-gN(^+UY(g z1%c_l#8*IkJ&X@JAr3E#?r$dX{q3rt;`l9Nx3Ic+wJ#^vVX&r){9`hPXJL64N@P3g zvH(oF4#2uiAt5t_<|J~C^94xIEpr9LZ_Au*Tk>(Z4DInj8$1X>!OU0fj4sByfTM#E zN5V+!iI!OnP>(r}hq_N;y_E!Fu#)guSxJbGEXMLcnG<>3^IJkUM-8}_(k=DdTW$Cd zAbRJI9VP@}sInBF8EeW?ig?wb%q0dXOe{bwL%6CekOc^BVG0j4X>?;>U|^Xkv0gSs zu0z`R4=;!zKQ6@45bu$bPWoT3_;d9Cw&Lf#c=Mjbq>7Vs2m{@&;$&VMC}(Hpl@vMA zvlYrl=VZ=QadOV$V5tvK&fJ`B=cz9{|uATYVti&Vs`$3Vt~2Jjdt(G2-k6WZW~=$B-ItM(SJ0Fdve5{1vlHv*???^ z_F_vn?4HVgX)!ywxckir#!g(Ry$4@837ld6pAICf1fd^_yJ&1U(vLs6warPThZRB6 z=baf%u>UMnPm<~p{26aMw?~Cnf^&XsBaM$Hk}2m;khwJmx5nvWT`=x^5r4)<9o`dM zp)#yk&>A1S7pVal_XuE=6OKFSNE%0v=uWLE=a1x=Ho;pnU=52xKHF@n-|i|jO=)c| z9OBW8fkT+K;H}6p54xae@f}eEhclfyHfF|2KWjf<$;NEPq zr(J6goQLfK~@r7tLd`>=vQy3he99SFo@uDYSYdGDsADgXc8B}ijviuNR&Q_OhA%bQ-zWxv34zJP3Kw> zjpQe8Fl3u6l4YBV&EPmb z&FMrom2FvPWz!--7A8Jhaq%^qfHl)kXXCA7bFJeFdu7LF$9)A|x7C`>W=H;vF|T!e zc6?$+4=kgPUpu~abNkM?c93m#Ut>WM2C_ea%-ux5I4~WT9hV&orgvwPvLCSmWr8Kr zZ1f`XO+()i0r2Q#E}p!YFx-_vo|GAzye|nOrPDUWGB8lOBM;I7SCysFcuIrecJiBu zwaoZwu{6=OAd_O?AM=)@Eg>!ENrd-JOp6X>M`lB&ewMvdQ1??P1l`bKeTAhit-qbH z%ntr>skL$U4Y;M|ViyRnK$JhiJ|kK_5g%-I7p#$w!{x7m(<7atI9l@U>?wylpf~_1 zc>rDywS~s!So_O9AeGT%7cv;IrSLflIB0TLVX6_eDo2<*@ZzQUYmPhA! z2ah>1ycEqta!>MQvE?L@0?LQY&r?wK#Yj>O19>Nr{XjyDGa?(H`)D{_+hf4ACMA>b zET;79Hoa$uh3kJwS4Mnp_$6H~mbGC8P}lzw!>>XzY=Rw`a=Cvhd#d}T)KWgr^m}}G zSVeYSJpkVxXpN=ZHxVl3K8nAxz{tMA=dSyxb(Ts;ucgXH`4BhnFkfm$=J3m5l;278 zV=f29oGuKogrLf@6j5a*Q&Iaq0Wax=kGCfVRViC~c^eL5R{2ddE!3P-L8yCTsOAi% zdt!ef_DudYN-~|ElKcu-RPBke5(kuI#y1!BC%7lZcr53y$nwgb7^5?LVyD6B?TN7x z2lm9Ga#sv%-zalQY_!aPNG#hQk}j-cWwP*8SQ4PE->#cs@sY=&VaD8(F-~hnLFs%# zK%!|%rk!hzoQ_aQEzrj1r!!h9p_TEwEOAI zYYAm*sI}-HqVZ_kI&{8OaP3#3Yf;rk%7{f0E)D?B@&MuhV6_Jj2LNY#0C50tjt3A2 z0H5>#;+O+XlB6ad=T~_#EbORlB1i=2&V&xtqqU-n94PGqn$>4AvROH6Q6GDJLaW9P2)G;eHb4{{F0L|x7fN9I7?|Ix;R5k~o;dCRi39OoZGs*vLFwaG zD!*3g(->acob6cixv#d?kS-BY`WOqprE`T6l~pOfsFcoeLiq(!azD}*`_rhX*ay7; zKyNRV1h`xb!?T!HI=3JYOsYG#GH^$kytqp#0j_|)_X>D<=lgqi)V)7QR~;vLg~XXF zOPqF9oPS#KAH~Tz$(~FM{sq$`l3;JNAN#t;BMlmr<(@#dq_KXYyfhVNnf*-cxyd_~ zyfy193|uaSUq(R|r1N3;c-ws`zI-*0OQCR9PF9^*@gbY8MaHn6tpHSgEJQ*}7og8- zbFloOg?OD(Iv*FxFG+@0?yO{ zE)Ep@^#GvYolhecYve0L*Paoml!aX;0m_r1-fwv_tSQfP5orj^dgX~UTu34D{VKmq zi#Pzd!2^f`fUkJ~aR6{50T1ifc6LD>z@zDQnPBV8ujB9L_Ib3v+E)GsL2c!m7(m8I z&M{`l7)d&=%U*z7EhgdkEVI`dj|HeTNRV?S|6B-Iyi8J$0FChyVAvRqle7nm7WNmtrAlZL+4_Vz03Uo(k}k| z&h93*g=w${&>=F!A`8G9yEQm3OjGPtA>cP5((&B(M8`W8xJQCRCNXpaSWgtz#bjcF zfd*bwbT2}#WN4Sbj-V0namp=-!|Jngaecl~(riVr9!=c`x*;>%pADwtL$^W#_jabO zH$HT)@0k!E`XM~dDa16#ho1F3l4Rt&71NcZSpAkXBx=cK4fbHsjxJXRPkM2 zBi&H*zD71QdJ%jm2Z+c-$kj*+_m94SFT?_5t$|#lkWmc#Mlqb>GaN;Hz$oG)ePU0Z z<)a{LdZ=gUnS%*AHn_#p%}qer=Tsb47i23>2+`as9;n9^&u;V} zf?K=@!~wvq9zYxb+~xtq!Fq(%&&W(^mkgA)VI3g4G*gJhaW()8=>+1bwKZ&x(_iL< zl#sw~<6X>L0}kvq2El=CMFG!JCRN*tf|#d>Zq%Z{Rwz7p35UI z0=M0Z*)wTBu@Ld#h^nzKT|9Jb)8-uRY)^Eb!bH7)?${=p#UmgtmZ$rZAEyWX91P+a z$|phTP2`+M5V3*(1aRByj}EAg=YNDT+b34jBZE@TYowf4U}b3eG}uIm+ZSIA)z7&E zX*D)_NRPt;e4rs~mCnZk2kqqOTc8bapW`YBaqXkS-2~jqS5c^%#=5+(^TOAOmuA-+ z@upE=F(Tvft`k3Bd8wkQ69->?kvQt$Xz999{11?KFl}|>f2fwGP8>7<+$auOhOUaE zPIy|1ZX`b~M(g^=b8M{3EG%s$dacqyIV?#m#vj}LF8(UX^NmOzB#Eb7eVFPL7E(S5 zYMsyG)UvZX<`L-1Fw;H=t;~6utZ)}LGju1q!P13jv}agnvgKhZW&lbYHq##==3Qb? zfZUhjS-h{w*mN6$Q%`svgO2D4pJQu;9U{Q{_wA^>(}UEF+s{i~RPK)gbXx^v z$a#W6!ii48xd8h#bp6m_IAe4#MY_4Wgxca z@vzO(o+(C`imeXxpK?DG*3$ZF40imIx$B*Ur+#Bj!$a@GLLdPtcQzLHv=P@!lAoNLRNd4VRpY4QDW)Q!3VoKF93-n7nq|xq#JI! zg5a&g9LKr?8!snfNzu1d>i(Hh@8-dy>POmb>}GjyFV|3`E^QGVXOsEOrJrGaug5C? z4NHFF|AYFW*6JBkwpo$QkH+~cg*pad-%#3rA#9Ib#)FZ{;w=T^wPfhftQY%$2gZ8d zNpRjoVD~`Og-oG4;T`}7cFnLh0sH@dKtA_p@cQT9L4#M3yk`tvs4~rj%-1<&OQbj_ zk_Kz33#ez2-C6hgY3j9l-$#v4OS0Vwqk zp*tuo!yg6Va2A#vHxn!6`3QLMA8wTZhF9Ru=cn7k!kzP3anAiToEOnKB>LXP0zHTX zH_+Kprix=ppHO;%5#25nX!VixvhV8LNr0~8lyy<(a%4%Fi#C=__{*I7TUM-eV-!;~ z&UvfI?J_6HSf1ocmbP-sc^6FGE5HZSmF;D@V2ve)Nh{aI+Dbd2**ZG_+mmxY%FYH# z-5p?pzCX(QHIY5oj{c3B!}aPEst%^5w}U{|t8+@M%hl*xv0Tl+X1SV9Z@GFiI8-fH zbImNUT#dYr$~w({Xm7F{hBZ^hV>x>u9iEz_@yW8_D7(sBNb{Ghp+Sn27qLxyo;CC! zP3)sECdfZ6*J#J|jE#$UEB}U)9@m7NWG3Ll^+wD{$qUvwPWwMw{$KM8SGFq3T;s5u zxJb>u1)JA=jvW2Cq^%hU%@8cJf7+ZBIS@Kbhaoam<7klTGA3%)UEm=z7I6S@w+9dh z0Cy6A^5WM$3-bD&k)Wp>^sEYc&ZGzV#69R)`0mO4a^!Ttmw}=J;2+@n3S=u?i561A z-Ao>kV%tNd)U{Odh(>0D$6gg4pA7Ke8LR8b;`3y2wX$HF1q+CP^*NCv8kvUg-ok@L z<{pfqa!&%IHZb@q8LTCP3$d_=gG_Ac{sP}VCMS5X!NqYS>SRrFcA|^pl4euGa}n$@ zOMAk&9@eew(ynMnP6wjvJHfKKvyHBO4q?9pb8LPPC4|6^g$mhiI%X6d&;eEa zs78lX{x2ZMkQa7jF?jAHd7FnM>G&!fwo5xWHIgoN#M?{PfKGkbUWqymrL`n_JOYS^ zsnY$Kgdcl$=!pGsfzUY_a8)7`w&obl-#fo%iD@<+V!S3(CF^Q3mDDCv8Thx=f$0qi zENG4D2n$y7e^W!g+=^C}px~8$D(+Ggm`cAW#V2)n3GxbhGRES}&{c6?g?1+)y_~9@ zwS33S&;`f>W9U^7CtZXs#HO^BTkifFT7ebMVW#J9h7(^HhP{>aS#UT5>6K~&U8>Yi z5!im_V;tl^tF6O{0T}I1tl}QeIH>hywsI@#?toe-i>;QkGf1?{!~Hp_^;1#n{Us&s z5-n#A$aEw&ZCgkyCwd->058MyD7>5tx`lWdvkm?<ctb|46O&3yj~zWbtG*zOE)E1sg?;j37dSfOCe@3h@%z-ccMcRq$W7Pj8; zin*cP9}5o9uUms;31BQVcOsABK6`7AOG8SW!j0L-TV?oCZntj!Qij^6?V|8`z zj&$QdKh4A%j8x>CBa!%o>Cs%KV0B<6SIZt6a0KUmz43vyj7CWQjiFk`9Sl1+AZ4X5 za@e^Rf6Di;A;#Tz0~B8)#g|BN9Vx!06iuGI;Bg}KpvCq#=bnjV zF`1Nhh7*L>COQ`2?UA|Do|Cp|^G%Zoj^lgn-_MP7TL$ervbwngkH_zTw72vCB~?NK;?7v; z!+qL6S&0ZJMKf-C_@OYDH!##O!%4SgNQau~!?4gC%Z%#Uovq9G4Z3Ok&k~Us%Z4cu;JW$gs>kjNpFHLws< zghFh&n2aIcxM7GBTRBV|T&zoSYXM`w6`6|fp$um`+~2T^cqW0W7IGT;_17>QU?+nB zQzzeoj&>O6Z84>vw#9^gb8>I)3C8dnk6#`?Xw|Zw%I|FB=Nc02fqV!*7(Nyn;?IQp zeB-||0KY!qzBS;!7w(7ggSO@KAfM+0{$B*#zk~ZN{IJ$5^eND`;x`4qL5!R*BJa#Y z5oQJ_sAo^{G^=MX@h~jN_7=}y)U%Ix-cSz@>t_aE5Kq+EUwn@l-+|)0UkMKq&u!{C zSUl_0bBK7ZFo6ye-)9tcI6Z|jBsfA`CAdByu9K{I)HzC=ivi?aoJ`?JxHy~66b^uk zZ8<|D_s&9b&4TL~abbtr`JlL_!o{lvnL;NSE*9q)I+uzQ@9ZV8iaEm3-^P^dX>>01 zbF8O-z43Eor+>I6edGuUoo5CD}a7 zLqruYDjY}cs&LPna0U@|1pxUudNckPj658x>3`AqIWp7#68)p%;iy~{&&wtrgNV8U zKs+3s$>$Zrmt!&gKQn%gy!3CZ8xKd_s(5~G;xUM*D*(j9(U*K)HGDbN(*FzN=eS8f zI%6q6jEC4h93$y}!}vKO(*H~PN96}cLtrcA^(&I0ybL1h3Sd+?j*(U2 zer>`TMAQ|430G&`+*yWN>dA9B=&&-79knJLmrg4amriR;s$-x%)j5zz$?1rU&9)>SQ!tAP3YSx@<_;(FtQd0)9seA|cPXSl< z9yn|d7ki85LNi=!&xSqge!a9E4-zl#7ttx<07!P#$HU{^n-dK7IH%)xE`Ey;@MQDl z6-yqfuBFGVTtIdPb$C@k~ls?v!iuSRlum&I3Fp{5x4~R#5 z-{wJ)0R_n3w|P+d*o5@+&4bd%CLH#@&4cGD3oVFM5P+R+b73V5pPg`X;Uoar+cp=L zRBFVL0J4v5MjwmyA#u%xi+yZ!VHRBMW19;wMJIi1b73l6>|>kJ$AUNeSX0N?$5z*| zJs=d0=44k5g_m>JKpb_fxzGmQTD@4Qw0hOn*`vs>6wa>`b#@Z#Y`3Yi+9p_MrCg-W zYO;lWqdH5AMP8jGE6@c8p>>$TC71L0XR-yD9@pJ`QR-O%dK%+g99S~t*b`gMbO^+C#VdbliGnpe;p*o|dZoe|EjV`eH< z9FOW&EX-gvLyg}WHQqrAs0ub0y5Z8wRh>SqdVcy?Hpeo)k?{CB17)L4ikUUKZ0gmm zCt*vH=L@2axEj{M=Y2YHfi5cgxad;2OJyGEj5;rpkh_;Lbs`OvioS4&wZEjh&rOvN$;KZc&5-9S6h0MK`gMnWHzBsK~I%#Ww z&1S^uU>ueEa+sftM#EW=i1265Q#1u%T*UJYI{eg_aw z^Iq24I*4mw6W0Ag9Yl=NqigCQuE#kk_lrcJKMqFz5E0iB!Ja)3`MJE``z0b+$YSfC zbDpKlUt0zVPRek`!Tkp0myC5j14?j%-+3Jy>CUgL`4NRRuRMh_dBQV?c2*N;rajG_XVbJ)v2mer%s*P zPo3H~p$f{f8J>{GmBb>TGp{Y_ZN^eN;r#)Vr~%Acc-9UL*hp$wBsvXC$&FU|bC__6 z?l5qwk1sJ|M#uhUJ7VLg8+(F`#^-l*12WjKfu6*l5F1w`NpWjWTYeF)Q?fUZTUh(R zjw%aPm`NL#wZ$pK<4NxcB<3~2WNh(wtZmFB?mmkB#L|-pz*~>zK#Y0^$AXq$$~gqN zV7q@t4DT=a$qqW*>0R@|%k5asreZM{yZf!RE1VZIxDe699cS#)<@fE3$~(gYol(8s4F9_jBro;3@4^O~Ur4hwVH`P(%3S!D!rw+PY73bK_hQIs z7=DF;Um&vEd<}9fBuD3Sj_7}%UnAQ$`eOs^OpIV~z&nnJA1jx{-HYy4)7p{!kul~E z8AmnHtA`jEXmAf$4&9}`oQ+gbr-sNk0LD0DMf#BT0?C&hzeo^{K34aMnILX(#k@7} zNZkB*aoMhMgu%$FBy=0(n#u;Hx1ie+#{(EU#PMDd&!8lQosMhKvy0=y===S0H5s={ zcQTygwz|-_#v&Jkt_xWPQEY)>KHwK$Lenj+(fwCu2Xx z1&QN`=|(C_f;OImN4+z6B*k%oaW4|use*yDko zpCBqnn%fbkzCNId*~8x7$zohhV+^bG1bp4@Lh1U!vu^iYEbxU?7)=adoA^m;j6PSV zG5Ve{Ow zCpdr5?+K0qtm7;nhkPTrxAgCTTW&kb7%!hi1)8Vv*18#t(XV*dG7_<9q=Xn|sf`3Z zaz^@45Gg6L60aDQ|AbFY4XMSeWdZ{4!B#-?g}6GEZCzb#*^hOs0#q zvEtrahzqI!eyn);%7z>h-dh=q+qYW6OrpHKAy(46ow4q!#!8mo)DSD>y@#>hU5%A0 z-_#H*?R@~T3foL}>GJy;qO^J+VwBY;N^2Rf`qb4-oA(h$!38kZU8b%4rG_Z&a@V=g zWummp^=9Rfw%Lb3DF3--Og>6~#e}DSWEKuide8U%CZ5mHv&MK%Iv$>bjps>v290Ou z3Gl2mp8gZzIm&pxN{^rBjFUp1ztW=!cJN4P#&uK$=+BNoJzz5)QXa|5ciIT9CBi>_ z#Nl_x{fH_-rDrgC-NECL5$OMJRxgh`MlR06n@2~wgXbpLKlaVq=+r&r=Q0vY6r5PO zZ=mx)yJsHE;g_QHXkQKeR_%w6r9N@<-dp-=>4JZSeo8yzB>NpkzX>{Y;yBgs<6lFF zL{Cz|k&znv|6|$zOQfIq6l^1M#>UyCIzI0YBq*ptI($ec*G-HpoYEHJHqQ8*4c_;BIe8bW7=@~F5|~oEcn7Gu)%sYx}Y!RTCcQoC1OTBG50pWafM{Mhp*>KZnh})^@ zaVF14TlG<%K80aV-U^=Fk$kj`oQ`3Alc_)3<29@{n2)*;T>uVEhYTk<{g{bA?!UX{ zyU2W3;QI=?@wdUi81HhpUv2z1gy8QCx$g+MzX120`1@f9Mm)PhevY(+@$Y%Kqxdd0 z{!V;*@yF?pA7?UO*1%H`Z|Oeh!O{*7ijdRr96--+jprIh` z$ls0Us|b+ zm}OJr;dzDJk5EjukCwE!g?`eGAa#vGt~Z^R~AgE ze;IPiV`(g%tQFZL_|G6%jiFz=1w6V-_dQC2ygg!&@j)LSvAkGkGL#qVmS}P1#X2-` z7F}MfTdHn(vF<>1%Zqgfsr%|szVdpV!TE*|Twbg*?3Y*V4%W2ts@);#mY3|La3~Y! z1=OwJKT|$=VAoXb985~wd$oLhberI#~-=}fp;oW|P7YR32K1lZuG|m#b z4ebYo-12}g;SgJ)izkTuXXv6aNFN^!Kf3oTJ{pB|f2eLT2W4m!3y_A!IgGPR%il-0 z;k8(EH2f7ak%q4Q7-vAsA=V)cKNb-_RpDYl(!}S|+?}TI^>qIT^!Vjit;O)mLDP>i zlz?|<(EVejeKp;tIi5+kDTh4CX?P0LngQ?R*-oj)nnwf;Me>NCg!+Onul1Txv1(=T z%geonPx5lFq>?+wpzsZt^143(h1f`kb0GIkQyqt3QtbXzNk5eCZqSAVhtW+Gws$O? z1GzVu1lx$oly5uSs>4jd$@v9%8V3)qA>~=Rg%rx@D{FapAv5`zW_~f0QOUQNuE zm>avlP|8oH+faTg-CAPudq;@hwpt+xjU4A-8ROomNY>GPm%7)}{cUxhPxp7!eG%Oz zqYWYVC3KtSx0&ukwTX_7SGZZ5)v*zYqo(ETed<0W*PvdiP@CcZqbzFdV!9#@xa{8sWj zdu9{!1-ea3rGZ8z$d4&cr<<7abh>fN)9E!hUu3T)ivZi(O}C*6+Kw^TMlIxyGQ7e@ zkj4z!^esN~ArNn)yNB_8Y5XyhfQhpOhA!ztLkcdbJ0~-hIrS zrNs5nJzK+lJq3{+HA8_pXqnp;ws^g#uI4?_b^nlIIsXx z$Kk%?2;Xre9kQ3=LT)wI(Z1stI^^y$mr}0`)?k~EgSaN@o0y%eXg(4ZUtZE&7(?hpDfhc~#;koC<}tiMU0n*fLR2p2XZ46Y?UT|hl?&u4fIdDSL`*G{E(o zkL!7Zt7A`aalHO#AZJJ7LTf6&f6OZYmv=IJ>3nDRt8*Vhg3-ff=WIp{K3wXLQTx%&jVg>D1>YTvNa0j- z{8UXtFB~`R<{;EB_-t%xxev-jg7cbwlpE*DDEB|G3u=ZQSIdNo_4i%(l`{D_PTCtd zxS|Y7xt5UWTAFj|o}upZ=$@(W^XYCQBIL=uvDp*3(wC?5EmL5{m|MMu2pR2c1#{aL z`zfpeHuRhULyG-sV59KCYpQj@iAByI`ODWJ3lSzC;vGtwIF1I8V|ZJTlP*}Oo?(@D zVPFhQmeRwqt|E^1-qsxKlcr#5n6ERshY`&jtNgGZOB^r&_y_>B{5XUN>Z9dHLTfCH zF0))NC<23%#1GiPgw4n45^X60+oJ~CA%cxQV&k{oBXeL*fTp8kBewNd>=IC=lEi2=~vMSO* z*;tGL9ZsU{3X$N@L>L5)JqE9|Lw#d(E9fk}v=6!j*r!O-1cg=p4Wg($WmF2|dprAZ z!?=~9+xTk?RM6=gsDNo^Th zVhS^$I$0+HAC3Z{na=!3_noe@AgQ@vc;nby#L0cjr_ z618L9W%WmXip`AW9e17Y@5$;F@-3Tfly~w2mg?bSTIfCpmK}H{u@mtf2(55Y7 z7@M}JTiUANVbX`tW;6`lHB2sXdkXe`5TDy*;rRM?ruID)A?WNq%x)$-^2S%dGJ^=* zPMj6Ah>Zs52K@@5*sPML4=I&+kWhpZ=bPfigM{K=to|Q{EDb`ZiB=L+tV5;-Yv7@m z&1&Ew8-xPK<8jRAhc!kBCFCd*BAQ^;2iZDsDpVKV7k;n}9t}Uv*f^qy8?#0jKVC)R z(FJ7P*U*KN0hoz+EuAA8i2j2NL~p{jiN~FV4QQhC;LUBd%0+yQ*DAA#yWhe3+=I$? zEQhzD7_7h2lbV>`+t87ECoqoGui6OO#y+?O9Vq+YHtmC%z9yw~!I*2K3+Ausg6Z_T zV5`0hrnrW>V91jrwnpC->Vt(&7iMZgeK3nC9bBkjzF~VxZM7GsjM|Y)QI?AI!(=O1 zMg;h$1!Dw;+Rpw34Us;lxc1=Tnj-@6JmTk z$!^6%dBWkI{4=eL<=0k*iYM=^(mz2`gp_I=eE_FI=pvJLX*v3}bUtpER?uO4 z??6alnVvbxL%eOt(r3VgE+-4^)2V#t!Y-YRxZbA#%|vlu8=4Er!QaK9(z_9G+(e3_ zewk&gXeaWqL}@dlGxwn4b*K&M?9I3Nb!JC?1Oj)|mcc5MLGpwLbUb=|_NYX?2(CT! z9wfp+=Ub5n^+-h27=QR-{sQc0s2m&T0X?e*p(H zN?e6v4k{@`cob3GgMvUxP|F4U5c^S4) z=)qkkc_Da`OqCgzSWFP$L-f^U4Lo1&DxJY7sp1I-#=By1t=s<20!17*cY_&U&Gh} zrQ~RsIYMJ~Z1HKt%EJ&g$12&F31G44a_esAGHlJ&deCZyD1G=YHbH*>sMab9kZbM#Nvl437tk)5AQW#{pZzBvd*H7W= z&s;f{^XiDSI|)H24%4bKR6mAZHm6SyaUTQw>-F($_fhu5XA$%1!QjIZf>bFW=5+7I z&K|rWC?+UUpt>YZ(Qj;#$ z-5Ni#H~=`)2Z#eipA7(&(!U2{tnyh52ED#~!wK0O2Gn0`G6vRbOvaWgK{^2X?Wg%$ z7*!mSrCG0iAGub>Mj4DTp|>!&)@1HnubqQ0*K79)@$_+t#c$1ejU2)P?T3gJUZAZt zsedG?>0@esYZhpCSmpH^(|tfXnV%cZoSq9wo*-|*Xz~Vw8w*ah z*Fu@35z06mUs^(Y79klhD7;^RqZ7!{o2=3gz@`eBXPnH^I3*vL@2iO8?Sn<@Z^CdkN?SCX5e?3nTs;=j8&?M{mZr7$u z*YggY==&K6Rz%q@XY$&FvS9C$1v`Juf}Ku(!Je*Pu(Q7kE!dl_*M&^?zrl&>o+;@x>vf7l>27R9 z`0I6cVzujaCsIKfUdK6l6;0XV~P1bku^{(7AuSg-Ro$pF^qZ{Ql8oC`Kj-x#dX zg}qp#U(Gc->9FuZ-J1}sk| zEx)buo65H(tA2_BFLVESRh&Pa4dm#*g+oQ18gu@$o^2VErXfgS9 z=h0g2prxMq^q`k!4qQL-dp*0n9yAb%f-Wr}!GHJIt_VdZ`Ew?BI?yZZLMl;=+|X&B zd66rFZeFv*QHIAN13CN0Uhwygq<1tel*+q5LN#@D<6?9R&_t- zJZ|sKf`jDVWFi&q?mYTN78mAQ;kLD$y-ctXk6XqDwr@Zg9{ z$cHh9?mfV30~8g~iTNz_&@LsOs7F8%N0;QvU@q6F`9y4;Tn5sx|NW#b44Ven$Le4(aC-kY>tJ{z<9_Vt)});Y*thCnvjCHg|4_at%Kfs$ z2*Kt6c2{j$8#w;XY zjGL8(s9PZXDyr$RMHYw&)>(w)|-XpDA$B*TphySeJfz?N1Gej2B)A9BHxUlyBtll$MivlHPZ=BA_A1vge zc$kJgMYJ5Jo~8qbD31GWkf5$-2t%ZU*k>}*!E{KZLju<_y8fM!%9pY0Lv|H$+=O*0 z9sUs3Uu+#1`3ic5(oT@KwQ?zA>pb98wD;>!PqtbcIUbZ1VA%Ob7~%Vi)_`NXXQLaZ z)9z@o-RA-4uuB^yyi|d%jZs_Uqo;vOP)b>Bm5DuxJrHXk;oXC&m|S<8mBDEBevJS9 z#m)g-eG_ve;M^<73fRvW1y}V7U0(*XFFwf9U+l&u@9P-QpUpV$^JC)v!JXCCSq|A;4UN&?MxIFBh)%RJrQ)3grhQ&(crpqMM}6GfA+%;|Xc~x(mjx=gy$5L->hjz{)r@z_$PPOMvX`)pDEu|;O^~}% zt69_HogeA}l@USLw5U|ZRz0ApvZm#)S<}+#uW8%r*R*7G-I}&u4~Y3#?kOmCbxq6c zM1~sPsUh!!6)o8qHGGU-zrhvjJ+vvp{jUP&=8i{|{Ajz=9(6p?)F-PkQb?`{ehR^O zg_PnMt1w%@0u}#?2q`vIQFRrB;qh>+E>JVL3FOy|z_(j=P0hVj9c?81b)m`_XM$bI|z%_Zi;8I%zj`lGQrd&O|iz!iL89S|>eHC;T;aLZ@FRQ(i`$ zFrWH5VRnyzC`zw|Ag1w&iMBjw>e3iVs17Xd0FL1Paem!1U;b8_nw)bB8 zHmZ-u6cU9uTyml&2Mer0ja39qO%?6ODso>9az)!NsG@5~2+HQSo10lj9j1<^X&t?a z#Q1f@1X@SarH=S(>WEIijxsN!j+jq<9WlHAVI93j>qxNI*AdY-))A(1J)Hk?zBi2y z?!QId>2!iuJVT()?=9ff)Rg7UgSe;jd%+&pe7IUUjwcG836H&%?fwzn6;{%1yJjK+ zQl^SOgN`{K?S#trOw~o6noAO|o--sP^OjWg%vUw!vtMmKGZJz`s5nl3_f3%B;zO(X zo!pRLt~S4!nqSYtaWcHOd4_)ZmMq`%{HFbz@?&59R@9r;8f>*g@t?JZIY?)`rp93E zeI^2>#vagH0o584gY8*qHVk`3nf_Hpx@)|lV) zmzN*<$lK61Wu_$#8NaQJ7ae;@jRI~F+`xNl?mysI#U961DC=Ex|1yhAXg+G64+mDNCR2|yz;n7Wa} z+7K{SdK$_IrBoE7f9cD}zCZ4L7oXNd>D{OiEFE!HVO+3)C9bXh!mh%ia;}6Y=b5%5 zg+X_8E0b1tG(W{Mf@WLnw7!EhmkEenaS4{@brit-*0iKq~ z5oo*oH?8z3a_93XTvnZ5fV0mcqa@_N=oP++_lx2s-^BYRctd>qGQD+tBP_(Xi<{9| z6UW^U&>3#8HF046=--6r(uwh0HW_-F^1p^=x61!<%C&i7dR`gO6RyWEvL62%%+8j? z`4u>|9>x1rdZix4`!(^h9>x20ctiF04SMV9k+4ubZf!mbrydgU8rMHew!a{VO)r`)XIPTd2oniT@i31Dz6XRL? z|016C6PN3}$&~B-iSb-889WzGjOQXo*_18aOG&y<1cc%v-XBmRuyFk+zIv&`c=RTP z@#ysl@xhUcL`i#behAkK03XR#f1KoTLfHo0o0odcO1vvG9L_NUQ((rSM z^wq~$UJn2vwYh+hIsl$`hmtT~M`0m>(y0d|>H!_~fWCUb@_N9IdO)N_vc)k)xBDDw z3y&6VdjSOOo{Ck4h0P*T1J$Az`Ee@$!UvmgBT!7)?7oEf*2s6;;j~fV!?~??p-b=R zMP=_mp9srgLHrz+wz)^`!c3X5%Hb#WyK!p$Rvb(C(E>m<$lZ*&ZSH+`VQMv@_dcZY z{)5LYA)xqi%IiK3sxYhl2MdFJQ}qiuj{5?EC)EJme-d_J0243q{Gkqy`0MdhfmJ*T z3-J804v+Zj@l=6TJPHf&{HYF)`0MdhfmJ*T3-CNqhe!PNc&flE9)$^>H7cc+er#w$ zQB1h!EaGCo>lDhvDG5H=DpQP#X{&TL7Gfe(92;69xM$7#6wsN_eShxS$2>7}v)G0O z@)ID7y7#1S0k30RRPxCXJDws(tuiIkdqxhaU<%S!rD)mMmstM!q#em4uZHwq3%19w zMjRt0*F7g7-R)dIVe`z-7wRq5eA~l5yiwm5FU-dE1S+Ko9avxcPM;*=UEU0@($nt9 zck;MsuVn%n_W+}wEtP_K6U`TC6h<1Ye6a=oA#?jAm z#FsOFTQDhW+*L#ez6-jT{QB&XdBbph{>Rnk&$F{?{(eR73xs6%#F>0a51purV<>g% zDaBP4pb|=TF0;|+Gi_}L{2(Xl_yG%iRRIYz2X&f@7xp#sp`zilLYWY+WrgtK{BXVj zA2i<@KGf#x*ZCghNZDNA^B$+cw~y7e1su8V+(*3RKTdUQ@ZKu@GR$#N` zoVvfwkIYl+{w~LlJw+;tyIrZF<0U!=Kh%{UU%f{AdMz|^q}sS0l^U8M36^Wk`3cmU z@H0RHRwDPEQbT{h)Yl%}uJ!0AP;V-ffK|#3rqmG4)8h?qT*R7U;Vz(?6X^Q@O%`CA z7-#QE1A~@>#EePIm^)8m!sbQK=H(gfvz`k2_ z7pVwh#|`}BCN}9He-4n?Z)5CaHFna#cMu*eWPn^6TEZP-{QMqltWReCrPFsA>m~BzT8f^^EsR?SdMUhu+ zi`;OPwkR^GZIPRv(iTDja8S4x11vF%xxMZFi5LvK-N8>Y`>hjk@AB zP|j!~Agj;~7>&C+p4f|hk@?26DS0t4qiQJVjXMyFCR$B<$Zu7zT!GwVxep;#!Rblm z*CWY0*Z|Wwm9_Ea-wff(@c>hYs=t-@bUD)yy{Qa!rTAWQ!f~e}B5o5x{|Y<&GE2$g zl>w016}M~%(=0)RiihGSEGUyq+(LW+sPO7Xbap_et`cy<%>}uAKqkj-#w{*bU;q2UR03&KWL*Xuf}< zdT~ZqUD87YmDq1=<62x?XpI}(DB9*Aha*GNk{Eyij>R&jBV2K0x3b~g(cxJ zAP!9>G#gQ(h5Y|N`6^>bbG~u_XymKZyd%T2lE~hF%xLWJHJZ3|Z8j+xv8ytoBBDor zkP+L+h#km?h=?=BKr*B6@$BvEwcoHK|IWG}r^62FzS=8S>ptj}weF8`3>A`p90k?9VT|8|HT!2 zGq2o3KGik@JLpHDgga3iVINxGM%X$U+Q`xWGd_(&B3K|jx#1H#d6{$8^GOWE*I-iQNp-s$1k9qN(Hd`Ioa ze1K(!n!OI!amx)$<=in!rB>?X7~GKRizdmb=e7{+bNkQZch)sM)x10NeazcF*id)cPEtC*O2^_{`$D@Is@DyR!JaNC3d?-ctd`>H^H)pjFt6dV zmb13jY*%V%7IHLGt9VcA5DZyD(qwYwub<+^8?2u?F;$8>SR2eHlA#A6Z!w*Cb;WfB zq`K_=UBL>f6Due?gIPn5B>7Jy!8AOtdgz6{9hqr%r88-4U}e%WTXT>?qNC1R^H*XO zkdKL8a$#@nVY*r~t&H9((F5!f{a9e1qZ;x3U3}68ddHjs5+~EGnk9o0xbl@rtu5dNpv)JMxy%>a%)GHZ(^i<0Y3EA0Ez>R&$ktFzuF%? ze>SOCwrkWYKNHn!Qab>D6&yE9P_qj5>vg%-YjWs=U~#BkaZd)7Bonx5y{b2;S6OAn z++&458z!n!%>K1Xr%C*!SP*2vQwWD>;j^+i^J~rbVy%`%eRM+Wk2^uE0}T-lCxyew z8V++4xeI)eZ#skZA|wMGPG!=~ILxsr%D1Q^c#*Ac{vEWPN%FY1?Z_m#zU^?@2GK|k zeW;elZxkwc-t9RNWhC@>8ic{<^aXT8c-8OwbP{h`;Uv4F0g= z<347BekMWubr&N@$Ldf=sOy{+I2Had+9;j_V!967<*^iIWcmdk#@W}cu79BR99kpW z?mDwZmbqPRpc4dQ0jke!PM)7?)f2~NzKW^9Z z?`Qbi2Y=i?Bfh>6ZsvpQ*aBx7#=Qf6{xR%-<8P(Om$ZiSCw)U@1ootcU|1vqMsf*k z7Q(tOipMRmFN&LoR@#wIE6h{tvgkxdn(eWhVEm;F=EKle(0l!VUP0JGVd7V>r|aE zq&LLPSX&+~Yjfm-O%~1fgrvM-hI0ZwW!42rR0|mgz=x|(xK@rEP%eE!3MR5idFTuY zqK%bN)PZDd8$s%^_1h}%vG96En&lHjar+b!^`WK9$WDe=7)Vo$9auY+GO4C^E zxdqOJV`P*cv`2>-|7{XKaE=+@s+;&e&d!38cq#CA3xH! zsgir3KHxO1NA*KZN&@P{|uvZk&eXA>%&C3VE=ng zut$#}scico;Uw3iT!3q=Gq|^j>Pb2J(cY#_?xkvi!G~b@jw#Uth6Zobnpj4T<4%P36Bf!o zP~#Fb%Nw)3*u*h;`K;<~TOaG~I)7a%oZl~Ma*?^z;lM3>Of_=>+k03F#NTjkH9J<_bJE1w@dKxH-zukijSWVK1K@g^+HxLbkwag8u&jOcM=?r zzcSN!7^Sks+dE}#7w*9x$wAfc2kO7UW2f5o_Y_+{n!|NZj~FHdXXFGpqu{Dz`*{Yu%nGT zzt*sV&%VZ8Dy0f3E4BQ%*qp@iH+4D%yi`k!d(BClfKx?Nz;jIjX(}_tQ}B!Y5WI}+ zu7jtxDNv@UM8JRJ&R4a7Dgz5Iz#E*L|8P9qS44xq+5(RSjP^7g`E@xzC@iwQ3_yi; z+sjE1e;4*z?jxM~5DV-mBL#mYMUxq~?%8eyozk9~Y!{gVn*7~i5{Mj<6B~-W3qZch5xH*Ra>Wy+gYNl*aWtb!r&V&p@ zh6Fn{kxxy;PqThrfe{|27OFe>wtF7d&)DFn>4_@X35-x+{b+Sb-C^Spt)XGPyb_p!eG4_g=L2;D}`lcLZT$?(I8~U!iQD zZY_S26{~u$gK}{>%x5Lsf8NqrvM!8sY%CYKnMSg+m}}NVrqq^ z#&XHH5(VNYE%h=(X>q*fGQ+1>8)PO#zs~nK=lE>E$EIgoIHMegY(tr_(C6Cmcr}|M zf4HqO+YR19{p0``=_8|M$*9knd&5{alJ!ysUbq2|CEQz$kO4j439Ovo%UUrO-4R92H#-C6}ytySM3QG7$%4? zHh61LERfBt-9d#DDL&DGmYLz1wszS%R><(v z6^s=+^bBF3fWD+?eL?dRM<^ULwu1|){H$c5$7nVpi1X#7327KTI9EOwh5Eu+{P)>W z)$uGS12C7BgU(cbrlGbKidhM*+O0PCY#nkAJqOB=Oe` zhAJhAzl}qclEmN8P*vZ4*$<7qo{&8K(MNJIU0|q}lMI@SKL6wL(%`G(iP_f#YE&j6 zwEPM-<2NYpuTgcPWPp;WX^jOD4?OBnWlimk{#e!%&jh+ z(v~Y(PJxo4<|c(thd3M8vFm?O*L=_0@#IzSR=?~U1;$@de&@90VZZEGOBjD&eqBp$ z?JTtINiN*+<4i+xk(q{MFlJq^X)fD!V}WXPxX6S`ou(6?4lggxX!q`3jhy|SJH%Jt z0;|4$CvE*zi0QZW8<0P&J~Z7EHoZCzo-y-%HBzPWYXW6ntOXqRbpkxnmfKcPV8s@T z?%6_XeFipj00 zT_<6?d!bZ#W2UMkOKW1hbd$8*UJ0wO`Sk|1g+pR$nljUWp#-Y3NZHD*myHS7_k;G2 z(J~V0(kPTOTBdTkv!#Dv-_pIF02G}%5_x>TO5XZ*0jJ^a(AT+v9ff_QsaR^wjB5fO zNoz~-$K9_Xo!HA2!==pJ`zP>YQV8RMgi)s~7P7NC+rZS0@5uVYVAlnUW3ja>d$ZBV z2~H&P_3`Su-D!m)_GL_!L~B_BPUPc&yZ{L7QOl7{yMBWc8$Hj(cAgy9d=DE)_1mkH z_0IvjxPA5mOj-H&IR0?76~WuSf-{Vp?k^ZP&vJXS$31OA+|Qy^j#zUppl@F2H+$^mPnLcx8z-P2Mh=4*4TT$R zAfUj0=Ny3bWL%EIH#y$FAiWzG9;sZ#RRsU2o9(F%YrZeU^Zt$&KIjne+{p^t{{?Zw z!CUn(+^dS>{8T5@ z!O)!Y7@K1pR%PsTA{wcrup_bd2$&Pr~ZQlL5X^c@w#@45kSj9TMI`KgWZwp;3KPc*C=fTM0IkCto!w zN`so(|Kd`G>3>_K|Hb~FdjLCRt9NQ^LOR=(5cSD+uJ!TuLp_~|KOf!>TvTT$7@T3; zba$G#*Wo)Ger^izk756te;hQz`IDw0=xLyd3Wrlem>5DyL(YkZuyf#_j_*e&PseTP zZSnGhEuE-S9hzBjd03=pTAW!w2h?(GKIs&9F$K@lC!OLgNYR{-RDP?Rv)j_24Nn$h z{s=vNmktK!;EVLs-S-a2Njq|6hoPd&gK_o{c-axq7d&g-OD&ZJ!| zQ}8Z5Iw_|*ERmzQV^DuM-HTX*KL{-(_?CkZ>b{k&1bJ~@-iI&G!3BSJ%#IH?;IalE zXIrx)ha#@7n*-f+bmg4iEk$jL(Spd|loy#PUMndE=1l#Quz;!3fT60ETS7<%S zcRZFwbN~**!TdhKxxBKXu7@quXaoFs3~c}@`+Cs8e|0njTAe3=Pt@u>iC<1 zq(PSe2?iZ;eG_=@YUFcJMqkfzW%DvRUL5#PtD?zDf@9w05M<4vaE?*Bgtd68TgE(0 zAX23qr9m_=m(^-s#=H{3Rr7Lhqj`y>)VYj#S0Kp4RpFF7)Li{icr%Lfd>iG_vw$LM zrH)Irb!2uF%@X)sC}b*+eaDBxGz@FgHN|wOy5nz2Q#e-TrfoPJeE!&l5 zi6v{oZ;umKj{&$G!)%Wta|g1+b5&zwmo0Jkos1@n&G*s8`n|;PV-L9SA5ykDE__IU zbzJzU0PDFR1Q3behfr0{TN7rEf~9@!sg<7HLDNAhL&>-b>woO@r_rwTR8BgX zD6%Y^6)&ug>p6Z0CUamOrpoeIMRA(Ki%S(=Twvh)arfhcOx$>UCre`1SF`256qE_g zJd9_Q-<`OeA|i(vSy%*lDeUi44OW{?Wi%ujo*?pcNy++ZYTlE|;T_@`6H*Cb5S8gDMyCx|l z*{*vR_h1OWd*U+6z3cH0p_KbeIl0jaIq(`dZSOZY11q%I-f!`hisXdZn&x(ZhP2fe z!GhY&z&#KvK;26lNQiGPX_X{#w-ZkE(CmoWiV2mMD+IMFov`HYhfUznZ-oK|;|j1j zy{pRB!HL4sM044iqwr#vku9t{_&^F*5%H`qJGw#K^q{S+_MlDGY4xBD^kcC{F_Ti& z9<P387wDGA44(@+&--Kf~(#dFwoerab#_5F2f_vu#3S-vb z?~Lvlj;+9`-yCn@z?2j8N&Zvoc6OVF(swEoi3N?k5GJ z2?`z1)6$O1*VJlKhDqvi&V`UW?xzHO!&G^11P(UQCBi z@Y>S%5vb2rk=16b=t0{1kQQ|`6)CXcq1#9?SC+0)w3? zCzbDVMsZ})g7@j&&k3M@c7ZA55N_1C$KZ$0nSe&hD(`JiT4$IQD(i(otQTg6)(aGa zs7p#}mkAG!wM;R2-mfqZ0MV+Q;MMPt&T>0Z|wGb8MNuSb(mm)9z8@HXHW6;Cx>ih$5GEhd=!5lthb#e^GvUC2X^ zQNmhGG7$fsT35nT5Lc_p)U88xZH$*W&5}NS560_ng5o_GucNZohu;SkmvI! zCC|P#dQQ5JJbwMoSLC!H;0fiF?dM$N_tUZXYvx*MiWyYbfz>cCICa#GIA2qOv7~%s z&%d|_#(xv_6RH>?d~G8mx$;xRh+(nF0VX=&DlPU8QEi*G4tISn(iLuK3 zx{>$Gi%&ESxU&_5k(!3x*$=0?1lvCBr!9G#LheC(&tX>xbFLI-fpp5qr~6|W@C>qn zxfLb%7WB?B_g>J*xx!4$6}V6NGt^@zwtZoQ7$KdJZ7orpp}idsgB6|b)?Ir+Mt+X~ z%v0LpaQ_Z&&w(x=UT}#HAVLIo5BQ!5{|yaAqL@y#a4eezzvQjYci1k@(}3P5ZwdJgpfy^ z+x{Fpvg|2VwDen$jb>BBr1Y*SK9k6j^sWe0d;*^AyRXj^JJPH^eojUp59URl{i3cu zvhJS|BOPG&c|CQik2hR~fJ=`1OGQk)%YgTnW8m!^FvZEb??ere{HUqtRjA{{K(u?6 zspd!v35=v7>8X*<#V8_u2OJPdOtQ`&Aw~6dEShRgwnjeuN^8R0c8Hm@B62r5 z(C$>(YxtXvq_b82E?yi-lE1%$N$8i*C7?^=13DEPp3&Wo*WK_0T`wSPxOnu>;%A$U zUo}Y1hXOUFqlu!gJP193M#tlT4e@WYRJ?D8I2b3pgM~0KZM@hg5Se8^^mdg00hV8~ zG?rO|n4d(v z3s*SYC&guTrwlE+sdJ!hX?2w5qT9lgZ+7V@-g7wt*u{4ieVC5qWlp*ax&8{}v3KG| z?o8d}Dr&i*jDizXD8VH9DEy04o%ZgjjFrr3?1YyGE)zMTk+U(0ya=!FDqG(L6pcv@ zjh+hxCTs3lrup>f@g|Ul3r3IQ@+5Df@ETSztDacKSzW@df)rl;Edq|`NLM3qf)BQW zm)Ho>=SgErK52u!+!N=UQx8hrQFb7gQoQCAqxS7s39r4QK0V$*YR$IgGYOw==8)_e zZcE0N{E6LPfd8RA!at(;H%|t?6B)(<@~%eN>uxiwru22CGATK*Zgup)nPRpt{)X^( zEdID?<>A0K{9T8?cj507`1?Bkeu%$E@rPkIf~&$}Y!fHsk-y<@H{|op_`%NL9^Psj zdLC37Kwbd9uL24J_*oTD6u@0oz&rtbwhEXpfcIAc`wQTvDqw*CUR4DQ3Se^;uuuSN ztAGOpa8ebpNC1aa0gDBI`KMnNzADx>1T!!`U`PO|DqyJq{sCs0;vXo0$E$#Y1n_eL zOsBF58v$*qVPznj1@aljvE9F*Pbw`J$a_PO6#}97V4cP&XA59^2=N;Paw#D_g2vDB zdI>nCG2jLXI1mA))#Ak4djXv{2?VRtNKOB=Gz|#4CXNE-PgrjFmZW3Rhn zjijsgr7PGl6QU9oE1eHKUC;-VtNLA0>}qvpq6Ve(f^s80w&af)r){XCF}s*_flHr7 zo6f{~m#=tI9L4rTCRW-mG(yF#t{`^<0&5^EzQJ$lPv{7{;=C7cxRF+r?ES^1q z2!@J>Y8kde5iKgmO7pOU=t=;~EEA>Gg%Uulur8B;6}0&XH7FhiWxSnTx`x!{m%Ay9 z05cYLcP2HVixDB_-i|!;odFDYL15GrNUSoa6tI}~eaK*2-eS+mnS+=lPs(_BIEX?w zf$=?5w|s2EE>7R>wc;II?I)39WyW`B}sWJ<6PhCY1Y6!sdf3`qXkcZm_*5 z*ytlRerq0S#ydL9fAnlrw~#2*_|UaA&^eat!9*8`mxrABwWzd>H{$rk{7Vwoc~R6P zbJw}=dhq#S@VPX%pUXL`;^OH<6gPn=`aUd}$N2g_?0yX0#$WS3ES>)QuzLeV=_C}n zGzCikxO_lf)xE2{9Qk3W%SfRi2+Rkzn!pslDm6Q)E32GBaLikZ*{1R;cvP8l@Z*3B z<{6-CAjNemq!UlyuvSrcK^?6S1vXlULJHCu@!mR@x5n&Pd3#&LiF#WQrmO-7F|MZM zkyw~iO5rdc^LXep7}qm|ZO8m~0i?M9`VIEH^vD;mY%Zn2@2&mrAi%m#o&nl8m$$uz zfNeo%F*6mDaGX&bz-Xw4c96y|*d^Z%^Udg2YxN|M(C6&nBy}Ggz4ow%xulGS{5P4$ zCNZxjPE5y%5|>?eSp*@1I|F6a2%NE2!?>RN z0?LH$82o-W_yBB6kW@1?ZsuxxI3Gw2*xpinW`RM!2^gfXX@Esn`AoCD12sC4Ns$oT z9|{)u$)_B)0PsWW;2>g#+YZ{Lgs9OuXQ`Lv-qn^Kxc~{`rB=k)>hBlCBJcljq>byy zty%-hf2%CKDw9?29I`{!WEGT||16)hYe@oT->I#ogFtbA>0tg^9;N^(C{M_zw;S)J z4|~f9n&lnJ&oupHz2%|keJqwHL4rAJG)2^%g6tjn^$tUHRE#?tpsc$eG;i?kQ@IO7 z7Q3u%{95h;gmt^vmB|li5X82eMbPydC|%uf@jKOg8A5-L`A#$6>E=7bd}o^PEPiXn zWghtxh)dr1R9qxX_7(mPzWt^uFWnWAt1>VfmHGWpWl~mNrd$jHRs7_tI8j~#v0$0| z=bV-5?KlhCDlrufG6088>LC{b4_Og-$dAB7rUV{xCcsAa>}qQrDT0QCFKT8VCKlQC zn93U0tXTTVKfnD{STksyBoa+l$U+MiirS+UA*MZ65jxuA6(ONLQIXu+lNG6#uKkmd z6+T|6YI~t&;Gz`du(SZ{)_kkD^F+UM** zyO)N5zEsGSTugJ3H?*%u+TS8A$KZ`mI-K@Dvk+q$&$quU1Z)1MD)q+a1?bk5h&0_j)meO~#kiMH`^xZ6? zxGkw0acWN>dx?usRi-Zh&+Q1$G=2x3!w?KYEH8s!7q>xZ|9TMabi8x|cwz$XM?hraXHv^mh65h&6wAi0-{57@i~2Hm1n~bJWw5=yn9}y<;zL`@{2I}t z8cWPQ5CA^PkZ=!z3xn{+*d}xV8`uTBgdDc!I9Ab?ATHBDhDn0-OewtjMM{RD+7;b2 zoQ5G;_q$+5Ut|ZC;Fy%Ow1o-oJ$R#%9FuVl#`!{}k+M*ni9O>a-FtyZef{2xpkh{# zn8RBa9+YBC4V$xZbxr!-X_`JJLt$SU>x11b;RMr6f}+#A8M^T6x=dEdEFPZNjodOZ za9|(>4rDkoWxEpj_GIt!Oky~DdnSPmm_#NqWAI5>W=0D4o`BY5whv{G2o7=8p-B== zUnWU3*k++ssX2;9VuvtEp}TN#%CIEb>#osK(FXKA$ixKTNfZQa9PaH1IK&>iD{a_} zoBdUx$+T|LI>=wGV9ln@$+Ydif2J)yABGUyR$taNB?J4t2b`BlXQr&~!mX_;uQOA8 z42K~8U}r#JTB*y=1FqGX^d|S8;NxXo?LmZgjR2hOz?n&BS6ubdOD|EX9EN>sh+$-J{2dJkw?o?D=0CREBk=u$$UIwV zDkiPeaoIroi|K(rX9wnc^ri9MIGbxU)LdJ4;qV1&xwAp`gNYpbHwzkM3x>3Jr`q*b?)GAXL*1 z-5YYLfULI^kS?dwasPsF(FN&kpBKWZ@p>j1FNE)Jz&Cm}HXHkkeFF`-N$9O;sq#X5 zsv(yqsY5i>rcUQiv*fz3Z(Wlr;@s;Da|@f8BdCCp*{6Upm>|ZAT|Vd*ACzzD2`7oW zzs8z)=~3X8<^4B#J2A(RY8Kts$%ClFLq z&-})K5PQd{E`uQyF~rHe2F3`kR)i%NdGXYW-1J3n6L%j56AIBxBAq`JM%=_VGZXh7 z1h{ve3+CkAL%@{M(*QsTCWs?G-gMW?T*S6 z2ZJ&mQ?Epp1ze^$6n-%Ujd9M3yaVx`L_FNQdkviOsti-K!O}ESh$^O5{T!|OVyCJ4 z%UJc4BM1#Pi7f8}NLNf)-hbjtn3~-8u7cOQ0^29q1ynSXi_xu#A{hKhgaGg1d6{gx!NP*byVN9}wf#d*;-+LdF zr2M4{oUNQ;a$CdP6-?qFdnDpT@eU{}%eTcU+BOWw7)z~k9-z!v4QBMneo`L@K96&g z?Y$U>y+@f@)tM^TQnH@hR?E8rVcz}d;EK?K4m`Yj+>=`|@(60M^bCY{2NC5KPqWI0 z!x8K)(IXo-;z(t4sp&cX%Kw9w8`LbW`?&IE#Unt56fshU_l_9D>ZvfNgk$R1gJ!HrM>=MTEo#BBL{Ze>kSq(MKmtZ4#9}Ze{XY zI#rP3fKT&ZX<}}dnDj9wzoh~iShpMVW1g%r-B(G>Rg5`KeJ~vL!9*{&KA6_j$E%TN zWo(oU^})WCM|ppdzV&LP##GGW#a1H8W=q##64ULT%(7W!DGS7TndMdx)mwmqn?2!E z5uHN#CMonO@S(4=c;7Fwj(wH;2ElDFY{@|(HsW!JuLM*E9a=YWy4M zUsLr{Fud1u`k?sA^q*jE$6S5aV3mOITEM2l5)EvAqgm zCQVn9G}{?IwHp8GAigDc9seqAW+QM>m7T-8%Q*wy?Snh!{SF76@|1>n^_Ncl{v(>pA~qg#J8UrcqDgP z_%S)+2C0n58s(2n-mQbNA|!+w-cZk%dEgsRejLf8o$g6_pwfor9N$B$V#Sl8Qo?G^ zIdDS?3GHxf*05aR=m{xtP4ukv9oN>-)b}2EC&%V-$vT@nWAP_PRycZ=vcNvc$Qyd7 z2(AvWr&ZEEehBBdm9cVU~v#Y3aY+2@EsV0 z^>%~;Wp^p*Upog%?d2tUEE1)DPywKM1BrF$cj;I2fL~ zU~DB+Q^Qji+!qdpr!IJTI2axcHl;#z*gHcvh;HS@mu&Sa?*i%=gGE2t6jqWTW(#)3 z8h}XN0{g)N%~0|}K#q&sM11g`ZXRA{-CdL^(i4pw3H(n1KlIaajxHBGLDWIhd~FXxEyMC0832&yHnTVkmD{G%ousv;ka5wd1IZj9j=k zwqSVg*mj?jKDraHILn1(M=aDA@OT`?1XVjr4t}~@$15vx0BCwf_{$u@UoP^<-&x=< z^8h`8->SRjBt`#HRHfVnsONa1^aJ4ZPQ%nCRobw( zDxmaur@@tR=6Gky*ISFvcIX^9-cRB3&PFK_?5#vnWjyQ!V4i>-eH8FeEg;^jaKyi_ z`sYhYRi-JU&o$#AWz_KSi!3W-ly}5MHeu{qu0qSh55+1MAfrZ%{)EE&VkB-~hMpf^ zhFW|XVvB(cvBgwnh;7D~q4j7mbuz^JJvB0&EjR1m6>oD5Pma{qYJJBP(apCGmxLj`p7 zR)Sh;v@=dlc1jZLDv+194;YAoED|Z&2a)>igGkXnE{+B~Ybe`%E!$!(+fb-%&xFb* z?x1Y20X0@1VY!^vqHLVl`elPIo|cW5S!&8AW;oRX=NM)Cmz0f2Q8ps=%SNOqTL@)C z*J1iUGFB=-JBHp7maW)TqC>UfIhZwu+kF~6KPJAA z3~gI;t7)cVs?9yBX7L{jT8(tYCTnqPpTfLfs?D2isu`}DyiFSB{lpmYkvH?O{nnD?_|#7EvT zV#b*>%lH$LjJ)h(3swd-a?_Sy5W~A#kehz*=afWm6usy`zxRu($NOc~EiElR_{ZT$x{y}>Wm91xWIzJu9z$}~h5K2&p!?zB;ID+EY`M(L$;>J^L zU*V@^alCCHO46PiPD_O-o%<|OI%Byn>2t-PuH%@1Iytlg=o3|t<-HMgn_C7V>A**JgF?JJi2Z@=sCYL) zQoVkm54_t44ox+A-pvZ??`l?ZdZg#{fI7>2HDFSI;t0_IQ$?VGTQuJJ9WaL!ZCxI6sl9>Yh0^|jAfXU5o1W(O4NqRPbo)qY@v!_~e z)Kfjj@xBDwii4K-WqxspLt2H>9Oor_62yC6*2uSBJMDuyN}xO zQ+u$CLVoP4KaVeT%3%xQF?8CxA8U_33^$L|x^r>4kKCFF1#_u!a6;_V^4Pbq7`^S+ zv7i_|fqP3(tu7>S@W~e#iNHAJ-MLo3m+egrel;Q6G;PV^+=T2`b;a?1RaWQjg?9a7 zd*^pp_#qN7CF8Q*imQd4U!!Q;3|YnRap;P1#8Tm5i&5%*cI5ds2$h^Taa(KT@H2cb zcd|xK47}Xs8aX%cauWvo3f*?MyM%IzEe0oWc0)x+psce?z&rl4+(AUN^c0~Y2MT&j zP>~Y@JqM^zZV`vuu@%{w)&16rY`yA+YDM-|?FtWDpdrLV80fc!PCQWH@;Rgr-?jX% zSHD$W$GD}t(FDBpP|L+w6O9}O+FGUEGL2{@YclUOeUJ-YY}*TTV_h9P6$L0=47o0C zzzxl^P`1oM*1pPogzSq{{JB|1@65l0?kDLI$m9k-M@Uaa-IU;5nTa_4hOm3CFT zu4nTtNCGwNkI&Vnr8r~dyP%Ej9YIXUOpY(QaB_Gp8bjD(pNAjZ{h$mdx?Mdv2=K#5N|=8J|E&%9-%yW# z4e@U#{#Pn~whQ1tRi($5#lvf5$KZbt@rPymOk#U0Mdwx(9kxEyj~7L1NIlf_i zevuLP2-I0spPwy3_4%b?`}+Jm2nqE0kFwkqC?7N&v}5qs8JOBdVDt&|OzE4P3X!2t z;psXKe3_lE+|Lk5)$1Wu16HhjI;5#0-ASx`3fx()4}eDG-j zSPqIQV|L^=&;Ub6SO>&PC9odbzvluMPwtKf&FZtg`!QQY{v)XB!o@J9$a>#`AFeV_ z#O}Z-+|V#2$;t9LV6@}?6@$W-Pon3gcA*Afyr~I@;<=YhwDc5^dzS-)Z0{-d!8_tP zB}n^?7eFU$*@6g%K9T?qpF zyAs}2_~L4jBX8?=B|DSu#n^SkID9V#fWTim1Cr=%^$TxW@S#hg++l5FO+boHH$uri z@7fmJ>ZQz7rGET23!ZSlG8DhJ$LET7J-8-YN~ec;hAjqVTGV?dn1*DA>djBY=?fcvo7mi4>20>XAw{vVwW`dZmC3+L=1r_Z7+T$_**aCZ#$xE!XzUlxDM@W)Oqe{ghV(knyQ zii31oR!6*lU$W|QJmu5?R3tLt}ITE!qMFxuq7$ZhSCnRw7X!VifejnUcvNeN*=(S z0DEsnyYOzN>kP1Zm9x?;_hsI51WL+(h%6n*yruOx*qeAa5XZ0R8uoF>%6w&by-q?{ z_$Gv`CwFfolK0WI+D9VGRi@*DvPrtH0PNkwbScW^8b6&ZF`15wz$WR|ax!}})7?Q= zzn@N)g*fD5zdxl#yAu!Q3TUjN7RcVD0eBXH8*5{lne(yD;-AJ2WOr3pY%i^5NVUYhndxnJ zEf56hRnZLb>eFMva2_(1Jt*|~@=rn3lCjDg5LoEcujocw-4(*D@&%-I8``y1zL3s} zI4|;@b^(Q0&*9vzNDkN~>T%Bn0D0LF&(Eu`1*1#py^w&?c3`LwEfgx?EbXA5xWj&C zV3lC*AXUNMIf7(?$AbIUfclHA10%2Hx@{-c&RZ)R8Rk^Z$4B?&5A*XLyL48W(V<9M zX6igt;Q5uaMG9O>c);$Bduj9W4b`9UU+WK7mRSw3*VC*v{JQ9wTdhkdIf<^4oGFGY zRH~_vb&5&U?ZrTv$JlLO40;V_9d+&r6dP{(W-g`}WN#{*MLK69V zo-;Fd@5&P4{r}(J_kH$Hd(WIXbLPyM-p(u*tSLK^_vNxv7ujg5a(q+2Gd_uIkzu=V zc(%Gs)kRwJiZAnbWAKHQN0e!1C=3^Q$00Yl8&^L{vfTX}vK$Y^kSCUU>PKPsp_4mB zzon?sesLZ+U+M)joji`$JWRMQo)Ys}4l>>e@N=?_$|(msZTD~#lftWH$Z<@W9}Cs@ z;+GkjpB=$OAp#5N7)!I9*GIzXS^^AKRjTSEag#1>Pa_(cV$!ARhf7+n#{5sx@;!Nc zhy1GDp?>qoCDv(KyHra(8X=3npQ^b-EP{eWGZ;v(kr)PIB!3<6L}eOY5mIr5Yw3R67peT67+km++I5C@sC?=cSjwvmGT zwgErf3rkz_k~}gq?zqbtoh6UY3m5lDg}`1Yx!~s4j}&4)5E$a&)k`fQ>y3pY9%$!~ z@AZ-P!7;eIVp;CV3^0Pt^Y*45) ztWb!VK&Uj76k-60GABCR0phsGIfCrR(tiZMmEYla+;{k$@Ev|9(k~@PukY4o4ws+| zYWqt(tS?KbE_qlh#Ea&TI=5HE>QyjTVpnN`9i0d)SQ?v&70lE-MEc>E3y`U?|HD6w z6C?aH;d#-6?hQ3A?8kUe<%E`a(k{_|^!xwfk8%tbFY&!+;|FguZHaOui_`A$(0wX@ zEMANAfyY{&<;}$+wtJG`4_416;Vz{tZA(sd)xw3PAICinG`NP6%Fjg5ofs6|jw3{P zm;t!miK9_ki3WGjhmuKXvA-JRzLXe9$#S|qzO3)IsqYC4Hl%jiroty+Q%mZkyNWMkv7YtR@R^*Ekesn!^&cKuNGG zX(_;AFKlX~O18(7g9ww}N~w4AKrLQ}?0cqj@fm#s;GPX;FR zfv{hPM35ba|3=61-IgvS?y#LdtgjhMif4mA+9lq##oBeGmHjNvITY^!9>*(I<5Lyh z-Na}@jUQ8yWP{q@t^nF{uV=X!P|FgG2-cQ;e!WvU+-a1^rO(+N^7EhN-id&@34^2h z?+17HCSh}j;3i{hi@qI}dkfJkD64;`G311E6(MwB2#WKbXygg-ZeyAEPD9LG1G*U) zSS|kvoYe_`0hUU+bC(-Ed+RuS=Hd@KACZGXe=H)C@dv8$llO}jY2K>N%Fj7DX_NV;#0--gQ6AhkFfFfz8^RsS%fovM1x+bFC+OwmaVIq{+XFa!y@r z1Rn1&EDQwS4xjo6*4M5DpmSaF9tYVWNnC!5|AMENVhLdQZiI)9(pIk9JDu8eG8&wzIL=DFlX{xhuu&T}WS-H=Si3@>wzAJiGgsp>Vy4#o3sVO9!_P79YhPv@SKq(#;yQu(A4f5pQN zLP}1@+yyPQSB;tc71kl44_qVZ-_3~ZcvrH8=+UjhneY!BXVJlZt?y{tde~3k9I!NW zOgrnSw1eeF@Tbf&_ATqeZ(WuLY-F3hc7*7eLVTD74 zw%p6$Bf4Qbu_KbO9@8(iQ7R{w2^u?(Jr0!-)^~8($|=<1zA(O6f<d3-pRa)rq_P@yXWO99TIBTmM>osFaUh`V?;WUOdfs}s47>HkJ%C32n9 zt3j5?&7CRc7!9X#GYp%t^CnMFQRL>KOuT2xgfnmeT7T~xaL->A&vkZZ;<-Y1I-cw5 zu8zOro(ufc-*RPeUOUfuWJ>H-!m@Kpm+PF2j2HQPmw_0V!t_HGFmL(-1$1@Sq@$YS zRzO+qt<0C0VaDLOZpijmBb%0Y2u}LnZoC1ZBCK}?$Dn|=sH@wdt_N+*EFA|5L^?JY zWwSDRwyq5IwDT96o$YhqMwd|UxX89tp+N&|aB|Y%z;u6q-UQ0S<{bO=rRmt>+1bTn zeXVfAd>x~ z=R^+IPd(PbjuBMpJx1R%NuE*g2}0RbrmS|d-Xo-1q^Ja*CsZ)ys`+)*Ci^ePd+M5d zjZ>`Urq=}smuFv*ha~Pg2IaJSXaSw}8xUPjSA&@V_D*WXJe>L z<}#{}v1I%pV`wv8oNSHhbkGenJGp(j;|?l)UA`_Tr>yz73ai|P$3o=s#xnL|`;0Ls zdDyVtp$DGih+e2LsWzVL=$?X}W_Ht*4e}erbDMRy$8&SlBT^h)A+u$am2aqw5jU`d zNT*~iA4kf|)+y)^O9=wdq(YMzRuCH%xRLf2gbG=nc#p>~fqthF{SMK}AjJm?q(KS+ z#URB;L}KFiiT-}duQ5pB<6wJ~m%Wd~muG0+0CUU7QFcz0D(|P`sC!_Kf&{pq&QM(P z7BAT<`VaOf$~+`SeLww8L|Ph|Ww7rUvPXffN1VV{>JtD6oV-^E0g$Mpf#41H_a#wu z@y<8DLoblO2a56q@+MyU{+OuH1@dDwvrmK>U1Y{@#Rc*ymiq-Lf(zYTS7!;CFLTYm zz)RMoWy*$m5Hco$n!|j34SuplO(c0#TG<%E;s(}#C&J*sUrB$rCHcuwo`O6dI|kl* z__6Hvu6T#WzR!&ShWdt~)R>K9dy&x=UTS97|72C&FdV~qLLT(j zvFH9?cCH#6va0{LD?)@QiC;QL? zFj(MuJggt^JRWJ~Br9qt!z=T;hthKefAx7hg62Hlf#6Va9*?DJ=y|*!AU>=W2KIGD zCigLt^dsz*n#?gg5@74| z9vbsOlQ~Fq4^00A z4rxEJDk1HsrN6fRM)fzwFLsx>=HG~0-~e%k)PS%&X27k>;I?IOyD|v7#D*3pe+|ez z1I?HE&>$>38*rC0h(6O`(Qz7ZRvF}uf#!fkB?InR2KOq1dzZmJ4Wb5fDVsI90ovym zBfC^542A&QP@#Mr^?@xWJWU9sg@%Z@3Q zkCR8yvees@DA%Tn$jUlA~olh)gb(Do5 z64}5KZ@b$wbQ$UdE~;3Dz}j3Z>r_B(AdE7~)B#wV+k&~z%t1UYjPpds38nC>tf!ZA zPyA|gV@P)r>1ZoT03c2#0{w&JbP==hR7p?^gY=Tp3i&Lq^oh#zCn^wHyr_$*zr%$K zU3SXB+oLkmQ>2%VOwb++(LMSf>DqC*lru=hcP;7e{ytt%#3@t+l*T#*{fM(RH(E;S z5~b<7vV(vRI;@ zRm40pRTA$DjCZue;3H6p()mn6=hZ{#V@2#sX|_0Lij5!5*sMXWWT-$09ZO8ffo@-f z7%FpZZaljl-Oy7HOhIR}i>=~3KG=rHGl4&00yoDq;#3n9D(>1`4&@w$9ZM!?u@1Dmg5q-3zi-Z$0hK*H*S@~P-4Q{7+)SF*$7|nTQY3Kf6io-LAU@g_Te;M zoGNaPjfbYM3hYe!hV!ph{?eKMDd@~KgrUG60-Qi5+v z$=G4k2QU(`@ZSEFb}tD1H50`g|Li^}nGDR=()RA{Gvv zL{NjFNd)7YiTExL%^^fu_Yz1{o4Q5!YfkCP9(KJVLY; zEs+5R5p02=JBcDzQo2x=#z=s9#Fa9SaQt&PGoZ#gC!+PF;g`-<+xnBgEAmUZ2!p@?{JZnnU-qU$0Z^^HwXwO@?q0Ki2eNZInK za^l2cXJ$+%X3g17*T)C}m;5hAzsq^B`z0b_ng}yO!WfD&b?S^N?M{hP)<{Eiv`D4t zHU-l%ow&;UtWX#ZI!rt=f-F0^O~4@uNBk%yOfK|q7i!!_aJRYOm&%W7bZJCb*$HSt zx~H>mqPinOW_g!@jAt=Ej9|CPoEvc>wT%%QMX_Q(`3UOj7 zq!nhN?OjUUA??aFny$^Y3op!EMmN$SzEST zA-;v~3Q=wyLUCIqgWKuc*~sqQ0jH{f7}u*DxidhO$?h5kcRMtyf|lv-4(I}Qn8~h1 z#?uojs21J3XYGLZ1}H=H)3q@71(+bd`va5-b~Oq@Wo(#ucNfZIcZg)BTSkOMa6PC1_=A3aGstwLS z;K14XFt}agB-&|GyZHc|9Y^@fv>Oj_YD8d>`xU470MgPqkg+CJS-IpS$I4CWd!^IL zrDofB-(Y{dZ?Fa2-1}dOxO*74G|o-)3Q;^0yRUZ5)keRovDcXJxR2u&5Os}(<8Eaq zw$L*@xTl!efMF*(iKe;9<^}2K{8ukcG{H`qV%GCW=%rgV9zxWSgAZ0`2j0w0vL>Z0 zobQN1*x8-A{jEtgR&F8AeWa|u-2T99A((A6W{cTa_g8aq?uk!k8dN*XHW$YZVQCU} z$yY6M6ObEv#8PJb>}ngDOe8k|Ng}x+Xw(^^p3gx`Tl=KHiq)U(dk^@^UIh4pn>Kx| zxYC*emPczX=WN?Kn=V-tg=f$2lN1a_99Gn zc1^xDZY9pPl4rLrn7==ECu$bNa}5X1uj0wVB>WRj&xbh3!uFEK7(}kFc=17GG>mi` zXcSD>g1Iff-D8vsH=GqhCC&1%8Uzyll7wF&Az(YbRf#!n`mzgVi?-Hmc;{( z&r$1LlA^3w^1ie5iLz&!a{8UQdUvy+1|l&3hRFJ|v+eBch5N}kV^apc&&tlO!}0m- zf%ExkQlDn^X;q(GoqV%>98tX?jSG5e0f2au2na77coFA5QYAqJeY(39BVt{#BUpBf zOo7g98Ps}5;^M8hFMe>G^ryHZR!BJBefV}&TOm$(PK}-tBVZD=&Mjh3Q^llt9o;62 zU)`gyv0QKA5(dL~IXLC{>3Yk(6!20NeraD#?+wU5VDAMMbVYH^Ha;FjteRCr0@2{eOb`XUcLa5p-JBoQii@~;Aa(-6*0Feh#FokO>oJ?- zwrvUztxN_B5uhU-YsaIyvCM{5^%jof+zMH`9tW7H5WaPBUvlG`daG3krfrT*XOsnz z&3hayIY8Z-+D5*7U@}SoAU-q*0T7JeteCdcjwW${^rMPAeS(@6)WTq##;{hg>~nJ9 z!{{*T7lK&v$sp`V6i)>dkb<#zPwSM-VoF$UViBJABhu8fWXI11vG;*7utmb;o=))<|_Duiw;M9LSe`v5p zpv+h_A|7U3SGQ-C|J{Kn5Q{C`J(k(5wK z{#;U_{~iz!3KOIhfi=%?AQUF3QUui`5ET##sUTOa2wDRILSxH$kgHJy9Ra~K;Sgj} zuC{*`{m$agjr{p=2*GGY@NGa)yKjY>b9IWK6==m_+7N=YBA62p95RHUUJ;xa5Ud_T z(4YvO4G4a%2uxPw8vCRBA^J)D+0sBwP32^c>0d(hrTqDJWBfc0C{$Q-8Ab4RK=Ah= z1X)E8osYm%_(LchMUx^}77!2$6O2^^4+jKn4q=)U-TQd06zgVZd_T0oI+0t2ndA<+7!Wd0Rf>f z!30IHARr(VCYY!QjtU3}g$dSC1m^?tDotKb0;xEY8l~cxx}9t9e}I0G1Mm|- zVGbSr>(Xym{sd5%V7>l*>32DQ0w_%2D)j9E0iiI#`ikI}0Rf>fL1+Kh^y@wlKLHdb z$oJ2r-%42aX>&QOwgqWE(!<;g$cS9!Hod{p)kP)is0dZfKZrV zk|KCHARrVb*iaF?9}o~K6Ri9aonZ0j=#EzY7ocV{OkMdET>Vj;osdc4%HLKxe@Exn z;G}$iPv^-~jq?w5W`eMPq?4V5BK{MdM{a1Gf2Q;9!1*M3FFhL9Mf~f4F|c8=1G@Lu`z;$wb`H>U>?}+9Dv1699og>km9d! zCIN@`^NxotE@yxlBc}8VV{rr;7`G4fg=&mR>5|iZ*7Cn$G=n$TZbS+ian|wAj0saV z?AGRw#Hez6g0vTkF_zPHAOP}?%dfn9(6uXYSkF_ddXB@u>JVY$Gz?G$F zP8LRMMACf$N`hH&<&QJT%CzQjAHsr2fkWHz5aNB@zQsl|k0i$RIjmCTgvh9B2p-wq z!f8>NJ+;UYVY9z-ApE$dX34D`3$7IYxMS3ezlr$cD2p@PeD45DZYn=zfJHWy?;c=T zP31cVSV&X()&Z8zR6aAnVu?BL0Lx=4?-Uy_X~VI7?lm#KZmtP_pZHit86Qh9<5S-b z{cY9X%z&b+)!$Hj&>G^aWqi0h>F%Qn5XY%2fS-o)6V1%B`LyNpIISe5(BmT%SpCNY z=g-*DB2JZAdH2DVw;pua9Tk+_Xj~OnTtkY^vb2K*S%WObR=iw_i*mGy^Q;Q__ASeY zo5z8CN-iG4s+0?6v&|jmIaF?v{nDJ*xG`;3|vT%=is!x zpW}BeL9ER1>ao42KwhDwD(<==ZtCeUe5WJ7v<%pdfHHFRJn5O{?F4%n&gZL3w^ecV zA_?tC*5OhHUPffb!{Fn1++vV(ChgUD)*=B9Y8)O>?pxvklXz0yf=cRJP>eeo`4i&a zv>Z1L^bfGT)UTS9?e{DFJS)cfB_25wJ%J{<<{=`nY>5jY?gBVSn~qh>t95ZPeA!-8 zI4U{B>Se7qb-A1ei6n@!g6Z*a4Y?Bs{;>GOq{uarBrHt@F(kU+ltkfq&bTBCEO{79 zZqF5822`DweC2}!18n%6L@^4}0G$_D>LC<1_o|u1@*m6C0Q&>;28q(qEa+%&)H9tw zG~GW`N3?Z1J+GhE`9 zJmOvhrhzzTBVH;DYS^#Bvs|2+?NPiiyv zbD{)neld9{zOlrrMP0GX$5r`cB6B8o2uFR4_m=zYJsPVmr&*=i)SZoymjAbT>`6IV z|L^4Z-{7&=|AIdD4yOgDRWe-t?MDIbTxDYmQwp49Pt2|1@}$bEm`;3i|K=E0sl2cv zJj0oha=MAb>%9HjbIkQc2FGeG-%_Y6iCTl67^qP|W$_a5%K=WhtBF`cgC%s^YX^|B z(6>F0g{Tr4AbNgQEh;fAy_Qo{#LITHgzx5T%d)awl|RUq)wYz4E3DWGbGllv?#cDH z$vfEX0@T2$oT8l?FHBLMOXExXZ1-Z2E!}t>yx#rowxTQ_|OIrR$`B0g3hu0wN;2?&Q6DV(Nu_} zE|=qpKqI#I5;!UG@H9J>)SeP4p*c~R?fQtIPxL1%bi_hvo~^hGGS86uD4A6Y4g|ap z5F#J9Gv4a}6t2t8=CR+bG6NAZa_?otU6t>HwqF5M4Py$73)-^bTPUcnQy=wSWpc-* zk&j7afAvCBByc8BMOJ5{3GhBbXqXvb^iHxb1-`E#VAmCR19{Y7<~}?#tj31axjI|2 zS>&LrIV8gRF3a&JWPo>DJD*f>cU4)%*TJGhffUNdm3NrM+LAJy&SL4k0RnlIu;3bW6auQkc%ba zu}B%C_vV&Q=c+|fHH2kHKtAvnHXX*FtE(p=U5r3NPC+&?sOX6cv2>{2Ht2!t@~!yJ zWW89vNcNU_kC{Y{B9#rg3|G(JMv|=JJ51QS`00Ge^54T3HF;J!k50#ub_-dM#Eur< z$H)7C#*8#Gt+D^=7xY@Yezt37E6G4L4WdeqwkPQyrAYznh2XUf~GYObV_4TR5 zSUwX2^Ws1dLMr|XpK0A#vqvq83}zk*)go1gHi*2F7$tQG$|FB2k%6&SYDdn5E`A{S zOWlR_RF$8P8cHR>WCAmki4VzI*uqJab>~1?Np}zhSrMz;ch^W=Un@LS&?hYb#qYXM zwE%0&-Vix#rKKR%xKVc^5_ViMp45KSqjAGE(1S_zW-475`iE{0BxuK0n2mAap59L}O_zKiN_fKGZk)?uRe`p>ls0->82T0x8B( z|7yZvo*Ch(V$3Y|eg`1}$G_1_u$iSa-WKpdy9a|hSYFr$`EBDQn9rE|SEI@F=A%Gi zB=a^13On2Kd^#_t2hXTyCS`2zefqYluXjIMxy*)jn#t>iiRDv1+j|GJs-?HR*8m66 z%{sUo9X;IppeRg9j5w=8{z3Fk`MIoa6Xo9wp(9p^3a#oZ_gl(>EjvU<7ZMwHu5pON z@cFzU29ibMm~BzxJ;*IQ0BUd~@R+oH&Oz7RcFVp|YVMn;bkB}Cop z!KVu&8p)PxP28DI5HO8V_l7XRU&;j8&cW}?q|l(bdD!bu#atM9N#QW(Dob(>#~un9 zJV4L2pYAGsyCIE%44)ohyT%pTA3{gk9wK?TKgCZ~eC%x4@mjn_$2`JHUMOPb1WW8+2ARDOnyw@6d@UK0X(dmhShzI1Ej`i_J!$dRiw>|#$#NT}UEymw6{2hzG z-jncSTh~2-eiY9=F5WW>TSM3J{Mb?jJ_%4`xul@6PnEGxm$BT#*Rk%-oG%QqzTd(9qg!ea7#!Be}KQzL5;l*VEp_0n!W$eRIxt-FTvkY_&WiAy+6iJ zSrtq64pgB*szQTQg$Ah#4N?^vq$)H>RUD~d>OzB5hCx#u8clUD=5Xq$lW61zP9(Au zbYgdn*LAM%P~BMQcByWZm<9XprbsG_w%Yh~G!o$-OASl-D*U~IzeWuCaX>4w8h;<) zZvv{xf%v-tf4{|Fx2?i4>?MGIA95R>!+mc6{bf4N;r##qg6n+@LR!%#_gc1KZL0Ou zO|^R?aGAw}w)ZN4-s?yyt*iEzs-*l*6)s~H7i1dS;4l}}HTEu8iv{bSbep}~2#e+s zSsd)WMk#V@v3Qa!#LpHMEhDmcRTe0EF(Dn25EgRf_c;Bc<3?n0i1!Aiz>d}s7Eh6d z_?3jk_z_uXUF@|Mi>Cu!OcNHZBeGcJy+tV+)?&fRsQkV`;kBZ<pIqA<#&(bky1RyRgg*H@ZO;qr?16=m0$TCu2@u!%;H_LK+iQq3N{*^_~8aAQ&K%_ zN^VBzL%sLNBeoU~Ha5!dzfiToqh?q!$isV|EU*YTL<}}k%I|u`qV{E;zjse%|9Tz5 z6tb2s=*z_8e)2iS&|Zk(A=+^Pt$5H@h${hp$N;iUtK1lVM(kx6mg-#{+r1rpF|4Y= z=tagzy%~_G4IQktn7VcwsHXi3?V4^kFE{OG?{9#t;MmW@7w)}G!T^GEv=>6{tT`^% zW`V0==ky*X?F#VGemMWwsKh<39OG9e#!zzdhiP#(T4Kh0<+SE^)v4&MNJU}&{3aX^ zatxmDq8BA}?}pPjlp_g0_oZ?7 zBr>0C(e53HS#49HM$oV#=MNaGDcc4@Bb#pSogu>O_4ct|XCEcQv9rieu1%uUj>m)w zWgW9Sd32WhcVc_mqfHR(K8hT6g!2#nE6S_ji52 zM7VG9*NY;r%WaeD=bj1>V-*7DiZw;ELR=LfS`^}}05MJ>jtvmw6#}bOnn$ZbpheV( zTvB0pv|49UN@1p!F;xn)UKvxZFk{M?8ildTm|BIwIJRFI8G?-TG1 zHHaxY)cRqn$NG}nRWZ5%q{=)^VX5}nQ+%+O%SJJOR0SicMO_IUA~S4=ZFHk`ttmjm zQnhKS#8T64id=1LAdnIu+|ol8EM=n3x?oXkE&Otr%{58%Yxu0V7TXwKy8K(eR%ljA} z`eg}hrGE;S9I{e3_cB;9la|Fx!2^+UZnOoG$$zrz_dY5R>m4(utN=aV$8rn2^_GyL zQ7E+(cBJKV4Uo?#%d#9T3Cr>vhV!y~-3(tYW4G|zVPPiz2-DOFDC#~+5O)F97M^l0 zHoG-PVBTz%#vvT`3C3$Sbap;!`K|aCa^Q9`mjA64L<@*btrL+edY{vl;J9pQmnaAL zPzk^jGPwG%n<+XwYIx@xR@!+71{n}kA5!ihhc>w*8+)}6fO6!nCtEG13@Wt*;>KN23(!e6E z{i^o|=I%6&(asdJGCaOU-iq+EzJL4<7H!%nPm7&%`5kBHCh`mYYodc68~Z36aK`LKfmP9FZttZnD1m=3%<3b5-sIm}mLU2ev)`ngKE%dMFVZ8qe+cN3{Bhtw3VL5UKR^=YI zCxOR|Nm#L)v0rvUe15f)(4w_PK3=oNp40^qbGSj@uD(K4<0OZOn+yU)B7d?c%>wb7 zHKQkOuf7t9?>I6IJQtRM{n>Id(y=VuEY#^(6W)gtU~D>y#DG-iY5$@iHJ4k^A@3e4 zu|L7LL^^f=nd0$K zpZlfKp%rnV!#zZYU7)}&_)QQ>HR^WpQ&6FxF)U6~f(>6z(=2Fqx;Ey1MMhs?m7W~i zSG0pPTKa7gXEFqim2fIe!>|GKRoM|MeF;Y=1|wvTj_LPc5S_wqHd~@BSsX}E5R%K3 zp;KYPut_E%aC{FTN_n8rrtwViJfvk$q$M}b5|zxsO~bDtbya=??Arb}plYYt-4vm) zD@q00PG8jNlTB-E(GugyE-a5>`~~D)zfha7*Z|STFl2t9_WIJ1O?OgzkI=YCfxXc` zfH|$FKprc?LEv7HC!=+!gz3EoVj6kugs92?4Rj$376w7(R3NBFS!FU5)MW*NT9+W9 zq+kPDaY+_xG`bDjbubq^6rBs9o!mIth^7vv?WXq8aT=wp23_uya=Ub58u+Z1`!zHG z`~IR{wbiX%Lsa|xP}Rbg0Ol1IRC?djO0QPr!^pHIwNKAAb)NmhQh(asLFJ;o!|ar8 z@8I<9fk7;{*)iC$(ZT<^-&koweAM`0U4+N*3G?!}i}HspF5BG!lQ5q2g;alMM5rDA;J3&9D-^0M zEhzcy2rR`#`XHaO94}kJRdLw>hOO;xH9+R^X0FwI3nm>3<|C@ToIvn6(F^co8`On1 zm4f!qJlu@66KcHtPlUu}ZpFb$WE9Z*Fofqhb{t;k{$STw9e$Y!FL&Ls8CVYkxFxgAo@Hw47V0;l6TjJxch6MTS+TQNP@MuV+aTZEi zJnkGy8lE*W$Z0T`QWqK}I6xT`n@3-hGyV!IxpB2qJq=RAYfj?6|F*1_QL-XZW&=N~ag_#2WTb0)tB z`UK(|AaM5Y@A$~sKf!zdfD@)gtuPe~Pyy@kloP`WO|EVZ>j}?@9E|TA-Ij{~KS5oU z-wbWfzX0Lz;$(#4&Md!tf0+mHy3)Az`)>d|r{fL+V1p=!?Q(U}#H}IKL#i!$G-&3{ z`#QCe?fX<)RGX{od>3~$aC!~TyjJx;%49ptD-~mK?2geZ6m%J!vl~$7>`vf0yX~!F zB#JJX$xss%B$De>TO*CTP`ANjx+!4l@yC`g-{u~S_)__G+ymv?;vOX526v%+tNOpC zP%?)I*0IQw?bAp+`}ayk4hPTv`SNY)Un<{*{uS~ahNmTa*M$@ zC()vr#gm?iOAC|9csMzj>WF)b@o9*Dn2Hy0DsMd4yq}Kh`#8;!Jw3Vdfj64`jgfyG zZB5~Kisdy!lB_z}h;dpLF0^Qo2yBW@#6V^fQt`wigP}(Whf>`ccu6RZA5KQ~9S08B zE+<<>py1i6t82BqdiXF9hlIfwrdHGtu{er_Y*R*idw@HZR{N%j1Vw?T6OM&wk)SOY zhIaA<0Afpn5CDiD7=!>oY-JDv0I@j{_|D+BJZzmwC$&I-XhFPdbK6n;sZ0*XPHbmx zVgbWHJGY%|2BC>&*cFt$$+eI)HTKbHCZi>ccDumM9fcnglA6f6*k4m;DhzrvpxtO0 z>uyjE=x!LmjhdG`c_>QxFs<8xmFP_XAhsa_-);5xhlW6avR1Yxo?2l5XhrDvwcITb zmugN)(Pg!Eq7{Oswgz4_#dgDzc7wXo0k}d$`a+9sxtoF1+W|jIRVQY;hp?A)3OM@w z7WxQ^yC=nA{tE!au0-@$?hnaqCL;%OCqpFw5IYco?~eMrmmv^fOUvIK2%Ze?Z|z5K z!U%}LsbPN)@oDU^+?~K3=5eNwqJL*YNB>YM+MZ4|zT+H_|G({#BySDkMy%?eY}VMq)sYD2SXvfMqXliko{^ks1XwKCG( z%9NJ7yCDqd6hmBbVJO;Y+Kq|l3`m4#PR|BL*NJH4bCp!&vk>C?faNn?;r!Ft2p>kS zd_ozgN%e@vjBHfaE4>u52+Jf^7MOg4Mpr~;`BacJNQjtc)@)@aOw`i95cwyg8Zj;= z{jTw+Hiq?A11nB#_aSR6tvfU&cK}m!1$t4u#tg78z+D-_)#hA|9}XgI3{@H{XecI9 zM=lF=rzg;JL4?x6NHCzE#}CH$`^q;~+E2btX+FPJ@oYp=+F!vJ6JDU;s|Zsx(40#6 zK*B`_ahE8CRg4o{SOyO+gNFdbq0mM6xOZarrla#A=$NrIdkF-?o3(%oO+ZR?;>>ur zfmHluXv@f3mGfOZv8}3rZce1HmW_NvgY&+Q0rifL%W`a*VSGX0%LoUJlcSWY}l^Dk1yipwGOZR z#fXRw!Qti-5vB!qJF!m{*@ttYYOqP+g=P5gGRCMT7|BvvQEI^E7=_GucR(jxjZv=K z>59QFI)XREd%1&DA9ri%;>=+aTvOvyOjBQiN4x>%VZ1MZ%I;Ti(y8Y80isC|tz|^> zez>cf%GCh_%K9pAG+1`k0hSzZZ>aL>fJs+L;VVo?S7|Ui+e^d4zZAni$sQRP^wfeN zRlFJH#;a$zL?RvSE8YNi1Cq(SOjmmi!f_Zz+K(sUlQ1^~+ffVmt42B-5nx)inzY9U zGtUHrTE`m$uZEg@OMQ)-Ay}*GN`0-Hh0|-o54^_W)Ac@v#5IGNXM@nNy?Yq)K^4$Y z9c!IaI*qqurHm#@s|1!RvEopqz`q6v++{ik9~@cLMYNf*kIrvfKiX{p=${AQbaZw@ z9S*71b?XbjIy)_2DyQ&I*Ln9KbJKOiI;+Czgcvwfl$v&!%J$@7=D9!z^>~Q>R>+h=ZISjt3Dx@= z#n*Dxq-#{{Qa5VTwccl}B%{-#yEcL26FTbZ>)bX(!fP>X#nLrWHhX^pJ|;M?jmncJ z4C?lwS1ioX4Zt880MmeIax@+$V2UxM>(lNbxWpl8 zq;w&mbWIXw@@h&K0mftFWqGuYrcF?)s8Ew7MG{lOyUFGj2GtnLXMf<{PW*Mkl1Uu6 z4Pbj$wqxK{0E5m?;a!{n9kj2ck2evZihSVqO3M4nl=RV*bA zrSp-1bOX+P*Mn_6X4GXddTz=zH0CFu);8i`xXy=0i6R|W0UHsh5l;?E9*OKdFUvV{ zG{q?X=pxXcNEdU=GK>~*uraTC5j>;JWt788?C*62zyp*#53OA)^9 z%)JZcNHN9Z8)%72Y)(0*QEk~)kv%RlYo*PR)%P0soye~rQ|;PnEfT|VP~duK#O)AL z?JlFui*g?zvM<^^J=)^P3a_lFW}E``RfWP8H-zm_p(9}ks9Vj&pr}V3MtTPCB1l_fA|Xc=3F?_HuhQ%}`5o|@9s3;K zWALSDqs*f$W;Uo%b}kZXG@OPRPIzP{{V{nK=BdweVU9Vgr#>(Z{H z#8ap$N(?%5lxVveqNZy?1j$eYhU;@{aaCDC?XY^UntDA7f=M!Uq>@}2d5eiWmB76X z^eJpH&sRw?F|o6+kl!$+mWN}SHB74#=_*N*IuU9Zems<6=O~#T$^+xlDBPQ|YDY`wv=Or{FbX0?OY0`S5e#AwW24i6sDI1*od{qRH{K$TTIUO&|G zq#{*gRGvPMg@YxKTi?Pvj38cR#7Eu2TS`Q<W$s zdfbjRKg2Qx>(C6+|2X)d#Xlgp|n7P%0QOX<{<{27>ww^!j0 z)YEx3tQQu1;V4W;q9V?i={zpni&r^t2<$Bc#7hqCkYj2!FtFph4q*2vXriO{ThQCf z-UhnHN_t)J=pTe+SUmp`{Ba^T>8&47!_iHO%<_O@Do^MEp9kLiLQY5Ftde^)0{31n zyx)O8np$+cQyG@_1`YRGVyBeZ!_`ZgO+)-=lRrE*3dnl55x(z|Z;YEs+YAX)0NHKMwjkJ7B?XmHxV6~lQSo9edxT&=4eMW;i?kxbe z&<)F_-hBE?_U2pdWjJ1Gd2NgdQxQHa%Ho#BrFD*Vq&%zLvHN1%cD31sW?epyLK@Aq zJbJZjL;Tpbu&QjwGP3|+;YBKd6lPm(s13o%uG%&#iuNw6qtlfGX`o`YLX*roqjuJ~ zR>}QVTuwpVFRnyncmx6M@9}_eaf<$F_#agEPoXst@6Oh2@mP@IS(?Vu5nzc=KR?BG zG~T5m!AxnH`YgwX@7E|oJh1jZBJ|13>j2v!99i${QMVRSKA%j{#*?@TL;2q0AURr7 zZ#@n!u$x*qEbaKE(U?Fq#0yD`HZqu9#D#UG6F zVPh*+sVRy<0SieA=7~p%k_$D_cz0ZT$2+;{%94SBDp_tAM}I>=7Sp@n!8Ofpe7z0u z!>F)08o0UQj&hlj^>hJ%*pLW(dyIzwnhgw7dz0ybC3&)e8x1cJ7npsb5~H=E#f|YP zO(9&|gdoWgQMM!zFuj{0bJTt?QH7TNsv~JCnBh0gvcVu9rb1a z<^5v78r zy8n)ms#n*B0W-QSeQwk-qMGLS91 zx~ge)wUUd6R`T0np$}sHfMUhaSH|R>N-YA6tNHT0Wgu3F zQC#izeh6eWj_>%h4I6#odwcSDUyVl(0*}Ll#}0-E%La;uN-m!%+&l_XP~ML4myw!3 zhx}B5K-=cer8At=sK1Yi5dmwG*gIg151RT)xtB_uJDWJea_<79$UV<+(Q@xfE?REh zOYnrpZiWZSate7>)lyMz7O8SG3sf>5^!FuK<`m1&esnM?bj&vnM+(pWbcWLv^A`}M z%X!Q{z_`iaA4E=)oTR_dz%Z_er|#(r62nI$5>an9BYITM=WK^~`X!z@ zCZ2W(i)x5ST!~~ZBY|rCLjrl>SY#ZU=RV~5lGJSvJd46}UYO_Fb$d&dNBe?G*X_lI zYh1G8&`RlS^4`h>U&k)n2)%ZN!;^;xS_bQlAf88p8erLr5#32V!kWsFEudJ z>6s`Xgnzhk=yWb%WcQ2i<{_@-64wDiT+p3PCo0fXsXzz9vjK00p~@U#n4w{a`u)a* z79r{vjZ4>^gBa1HrtUDGj+D3-hT{t7)4}kNd|GBWQU2w|q2)h>Y?)7XWDoGflcZ?> z76m+28-TpuNOZNBJi+Bia^W34>gPv>CHWs^9Gd^3cWsRKiJEF%x8vo_y;j`+f3x#2OiE*%LE$)jTfKG5Mx z15;3x>!lJWnJJ963Xjw zhG$q8#{((4IK{Bgx;TL>Lb_NfJWez`M$pAc@K?I{G5M8saVnjax;V|msC98NV+?e` zdUl+|dCHJDgL?L3c(9)Rq#RY!KizPpUd}KMtrwUdF!hY>(DB0mw17VpGqfH02|RT> zw8}7(oIKOGq#ZiTxO7@hXGFRkibl3VTqj6eXPCGURk>f9AkD@q_#tc2|C~*3fwHvh zXOiWc(nhd+pD6s!3h@iZGG_xRdCDgU)u0bU>Uh*Uhb(lSt^kjdgvYst2lJHUTnuxS zY%-SIMfEr5fl7@D&Lvlsv*=pR3*wN@=ln|1+4)>xD9W7=Hhxn*H%MC8CY>yDTwvq~ zw@DWQ$@cC-GBIu3MTVm;s~3@-Zj-PC9yta4Fq7zra@ct>JgZf*GpF0KRxb$MM7B_@U#XkP`q=)#W7M3|qbFg>Ebt96*`O&El~Aq3xO zV4WA&kk>P!*H^#`MkCO6T??erYYX%`4s)^btE*cp=3>3L><;B}I(8kX)pYD8GFSR^ z{F}p$Tf&Z8!;ahN(E4=z+v!sJlnLD(bcYlw$U7^LGLyT@ge%YFu7_AN=7jXivVVr8 z?*@~;aM`~RNS6J($;y=ddkkM)_HP1_Df@H5Z?KAML^n>ECbB{O&dU!272m z_&x)(&%S?1Jhw8Q2g>uHGbNteOgtm>2e-pt^#>1xm}B7fAf164rA2+n#HicQI~b$x z529i(@+^t-&LB<Y7W=2`~p?M?qbYb)AoO6Vy5F^<51I<@}V<->T((0$AU35nj-RCijd8+a~1aejRo+Wov`F?I3NRH$GB8V6*lH)%|XHXhA zpLhJ{4N18I{fy#W+)2xMJ4boG!IQ|4MF~F?S;7xTmZ*#@F*Gu;;l@GK6Ye*fwvReJ zPtyOeN&nDs=_BwEoxWg1rA~io99pN3QfM_6BhT}N=VK%BeBAK-m0?4kFB*sD`9zuL zGVr`Wcs?2C$#D$&^6Igxr4!#?Rf?xTq9=_n8J5V8nE$eINpXM0xX_Zt{8x>uT=<`6 z{I^Pf$uwOk@jnxeKRm~J79OIf*9<47>2>4KY5F;NDm~Gx#YMvN7h#@5(sZCo({muv zX?nx3lr+6*T#}}@j7!q=wsA3m@6f^am5z6fqnz63DcLzv*P;=wFAboHS#Q%@k@p0 z>m%`e!|?pnup!USj6?H$v&@rY^~;3kTVb9o4=Ccnn!(%f)MNF}4Kq=}7siE>5cPj; zTv{RTFrr7fl7BHa1o&Oe?|s@1!U2`JmU zMt8snHjWy$r%FV~gV+M;cDqb}siAk@R`f(DdO{)kD}?`LhQ3&h3wPj8grX-jGQC^% zALy?X{+9>*--UL-p9p>5$rigkR|(yfK&N1@#&+AV1+i>joa?N%RuGFvE8xn*==S7~ zAdcu~eSC+8oA6!X7Fs@e$(AQBy1j~ZJllH@60zTnMc&2E+SQQ5#to#af()_cF`|?+ z6<5t7#h>AXN>#LQkfYw{JPs9M;7)9K%8A1}01P@0f~5F9)47s9?tYMa;I2xVhe-3G z(9p-751MMr{Qwdr-2LJ3K7l9RM5?OBT0dj)J_Vxq8GcH4BSc2p;f+EBN$+!ba*^L_ z1AU<}?voIU-_dsTNm1_$cuN@#1rn}353UuFdn`|i{hG1e!`S3ZV(+<7cqlM-IVDOS z(3U6rs( zM2&5;baFlu67qp<+b`j#&D!d<`H)bOq8WQ(SZu`yorkIC{{p@HAyli&@l+(^8su|y z`QhpgW!CY2j`kXMqH3%-c2&PZ0G?#QCf|Ap1`N#A35NGwk42qU_qWQofv!NY3SD+# z_3YeB@61I@^u^A$U}0EQZZjwg(pS;P5~I)R?8?;)i8kYU{k?%-Txu2@DxtQ6{@8OA zZ;;=p$#3G9>xx?-jr-kw<-;<=)zP8hq>g@%aMwwEUsT37U&Ti(;yYlt_=bj)_(lo`<=qZmvK1=; z5O))S?>+i^um1j2fA7=Z2lV$r{e4J(f2O|=>+k*g`_zKaRku zZA4D+8J5#WBXF8IA}9C^%jvNZIISF!6MTl{bnFP6kZB|62|mMeS~EDj#&MWrBu>@{ zoc=umr|5{Bq9btn=Lnniw?c? zC`hd0?+897;O_}OF5n*sJ|W;A2|g*{p9nrB;GYRTE#TJ#pAqoC2|il}e=gu(i2a3t zewfPW+Synx>jd_lm!6a1xs{~-7)0ly{qqJaM-_>zGCBKWd^|0ei~fP(~I6>tr~ z*945j0KP8ZD1vVYXc2rgu%fjcDERAu(!o|U3Rv-*2&J+s*UmTv2=(fw%%54 zoQH)7dT*eYgjEmacngl=N0{uC*g0Eax_82K?~$$-YZ&-C8$o?CLi3zd`w~AGu zDzO*0iUSxJme`S7#jVl7me`l$ktN1-2f^BS=`MV0okDx9lbt*!=#R6~A(!riXJ`Kt z!Dvg5LDMLi>I@c(B@bBDLPfMhuf|(tf0kAHj~>CF1(^PIBlxpa(?30eKZ`p3>qqcs z`KN!w2>z@T^lu!&pEZU4V@B|2^`U=e1b^14@A79wqkndU@T_w5ZyLd$^^pE!NAPFm zq<`}W{;aX|ZyCX#)tLU{M(}6drvLa6{8{1Y-#UUnTLJp#M(}6TK>xN8{Mla6f5Hg< zY((fkvBJMCD8DS=>H<6=pN$8}*HPr0JB&!n)`hg|eh*qUKcsE{9<*$qNZau}XxV^~ zcD?UG%NCBb?*B3^n@jRtzswi*DJHtHHY_9Bd*&Ufq_qPCb zyTWicl*6%sXSloyr`i_M%q-KegfH)J% z38juaQT#n4$BGcoa9WXz5cf@p8<%qDi=gbVynXm4HMWLR1>C+2Fc}X!w<#R>~SoF)MaA0?vZwM#!f~XOoK^KzxKXh) z&#_l2L93LYRlW#XB_{^qH>{w%@h^fp?sUX8?MNjkZq)Szg0e4G_U)=8r=D-_Y2F!G zwYj<%MH^SAf#w#`JFAhmDR`yw3y}QH08#Io<4e8Iz!xJI_9h8$CLDqCmnh})Xo6Dy zN@Ojx&SMGgon-|pIw*JxghmWo;?s4$((XoKq1FAZ>WJqnnb`){p?vV49Zbh~(FO9fk{ zT;7v(xrYg^TH~G)SBSeZl90T@V3I9(z4ximQ22Gk0U*NUFomSoHK3!|1(gIpA0VhxZMe z4rxn(S!ytV4+o6pA`Q7mf-P)F;x_5ttiUNb;aofh<*UI|FBKVXApHPE%W6s|i%W1j z2@H+~EB81s2qQ~Go=IdHw{1P2&XdI%ch6T}pid%t1)PAF18$&!jXoM^z#R`UXaI{E zqrH8AfhmbPDrSdD#q{3Da;sEcr&OM{U{zu=nBs1Go!1HzfLa4}UK<=rC-9j_o8p&) zt~kJSqM;V0?oOrFryIPjn9g*adkV#EPS>mS*112X8~9U);D0j2eLCD9R;SajF_PA1 z$Mh8yXGS|(6r}k_OfzgqcrRn#gETkx!t`S#U7c?99Hx0pdJNNi@Q^fL2_l{5F)GcO z^cXRNf;DP)4f-cIw-)iosJ0wNUCM2FR<)5$=_WaI4cZK>HQ>{PkJi-~kES2AuEu!d z;1FFQ44cd%+x7;mriHkso21dS#-+1LRb#w129u%@LQW)7ibfgfit^G(ueUM1&x7s& zw*qb$n;z@o#Bi#iIn&V6dj|45JvQCqMW~>0>2X~RR{u$r3d$d3rN?<2gGa;o`c{A_ z)DAwg@zLoU=goyf(&wIBZbtWJ=8sRedfQNYt?BV9CF5LXX4uU_kZ!@&NlUsV-8^G4 zdx+|E26>&)d9CdvI5WC%y9jx`ig}GIQr>J;zH_}CI;3mTId5Mkr7hjYq+B&5DY$zh z?Ma);XuMtD=FMcvCIVO(3lh}k9Ri2Wur_Zo98#-`moul<(K&Sh(>F1_j<>CpjyCr! zu!7(dAov6=cn+z`rE|`V`KpkfMZsx8WR4Pi-QHU%cx`%JZ$Ap&o^EF$eWX%wxjv_5 zZ&$K+Sm}0eOUk|;0319qnbGdyq==Ed-NW$;T`{kv?07pwO4&lnzFyk(wxjI1Y2;kMJE|N?z%QZw^!^Yo-Y=Rl<69g>+X4p_xh!-G z>KDsdFC5kQ@*nZo4~?7zzjd+B9LD2ts66lX!4N!JkjO%WyBXgweJ^mUfO|HCr@e+} z!T+}J>T78HqZg+YA~4VqUkw|os%#{bev_4b&A9K;*Ce`PA8bi}3GX~)GcG44ybAyn z+Bm$D&L)OtJ%+oiS3t3Y8}iwr|yb9(MY4AMTbPtfgSUGM!+9r}bi)jp=v>8aQ-GmT+d z>7gF&?NqcJg)tT&8Oz4dD#g-ii48buR~pd9OS{sTX&3|18_?iLqmn^A?m<0XgmMVc zGv1*9D0)Vu0Roe3`9o@mTv612UV&bC9PhUv^w~p|8iz*!77zdZ2{YT z-4hX2A|no@ zF^M!LbsF{ArZi4!&6xLZIoP=c4#=W#MP{PToQUt!_ztZHxQD~JTY%@d`DW1GF7-gB zP96qyd}az3Q!x1Teu(NXCL?6x`4O5O$@VJ8+Zx{0`8^U|0o?^`g`zqAXJxCD-z4eH z0u}D9!N9xM#cltV)Un%fa){SGGm1NO$CjD-_K1!6r`R4oRORVeC;l2^eZgg7-y&tKuFW z&PegZ3GBw;r5ZTgODUmj(Y7v2UA>Y%tTrV@0MRo;%C&Y%(;S1&C+) z$;cyYZYN-z<`S~6WUK8fq$8~x7{1ZS1nisLC49NQH}EL7=H-aPBLD`SU$tXMwLLPa zeIErDrlw4CBc)XJGB~tDzl=Sg190pIW9u7QFN%=A< zcNI$dc)JND*D(j4tfYK}l(U4AKHly^$vN1-Qs0!P;zZ>;OR=r*GRdS zP}0ZSTPV54Kk!T?^UKQocdTIYLPvZ>~_jO3I&CQoc#beT0%e z-aMh?8s5M!Dk1O_*T;!r^>DkJ&?ff0|PI7ASKmJtUF zVo4cslpqc(BlZ`>(lX*eL7?dcQ_h`3)Q!7}`Gzkx2gvIk)-?)eyYLBa*qOp*}t%&3X}fPBsc~?9q{(?6;{?@W}6Tz))RjP zDbZ74#UMWcCb0(y;`L`?M)hN@;8@bE#7_qZj>EU>O$df(?td8K#=7JmPs$VUgSD3l zSbI5=f0*f3B*dSiJ_kM=Jk&E@#%0Kpab3b)$9Okao_YoLE6DX+p$Lmjx) zxmOd!0)%j5VNa+1Ye;h~emX#K9lpY?ZZHG8+JS6rcl_%~c>{i6G;TtDy?Y};GudTL zNjLa6kp`nwaI1H3!B@D|4`yIJEa1kr$-kA9x8a9o;_4gS+X><+Kaq{qEIr1*gEV*I zrvn6c;Vax42Q#n{6mVnv<=;)pd+^hc0k@2MFF`X`WgSgt{hyNNKKyim;C_6CTV^l= zt0Muowe&XE=#8`72gu?@tVDO!8rREk6{c7|0~*|eAZr+lRU^zVRUke52_ZNJG8(@W zwP=XO*Q%3qO`XJu%YTS+VEKe{XcuPbA_oH++|MXSimLX2gn2%Mn6|9J+Q;j*8kLYorfw z4ET*zSjQGyoK|E@yRg*2tL*NF4g9{{or-Fj&S-a^fscxIUvJ=NquqZoaAT}{%b2EL z5bHk6z<0*FKQ!=Qth?Z7njM|)V+_37ImC)<{KWVnry2O>_#yQP!E-)#KgO$f%Q%NC zeYo7xy9y&JC%(EmF?A&%HT_bvFD{$QO4tb~A$sP*WpmD4WQ{OCgV%S|yajgv3Y~iL zW)=#$7T3nL4>G{ubJ&?`&(A(gB@2!obIY|Hj<*+KT4DLzj2n2qvip9A-S?x&$o7@= z@g4)HYpaj?mGvGM1cUld0Q8>3PeT&5G3h={41PL5fcdMcjj&G&`x`-R zT#MUW!wqve|L0_Jzbbv&^`I_&3}`5R0Wz4)%@r^+Qe?XHg%DEuv>Ysb6>?xqEc1_p zBBPg?F?oAr(JD2LwIW|fy4%!Ry@eUdAvc!S?O`%5f_H||;0KH$~M-3WY1Z27_(#HLlNboX#IB0kUUo&V}64EF4PN!}H zp)6mvQ9q(ozpRw%JxcW|7__gXkM|ltO)hQo@gmymWh|NaZ@}%niJ$8Hx)^G_1qhv> zL=uEb`fro!9sF=O@-DtzuR+-uj$rZ@WV!_6X63-IDkXcLlD&se?JMcyy$>)P#|a|Y z2W2do_#eXUeS{wjY&dTDm>>o=W!V@Gqnl4i_$hu!^%=fhuc-DAb9*hEv_mf2p&cWI zqs(5-?;;zHH~D|csL2ogKkU5;oLoiK|J}K_r?;6&re`KS$t0N`5+=FK5+Gqo*b)}m zRW@+~NCF}VA{S-|2u%kR^>G0dT#&dS0xIf*`;NE*q9OtxBK?Y zB;xZvzVG{aKX3AxzE!8HPMtcnpE`9assDWD5#%@@QT<0d(icZ(@lfP)91H-J4qje6 z(X&WL03U%?k)<({}w{x>(Agi%2k%%^CJJE)$8<|j~tI|_mwYy?>A$Bz+h z1TlTQ6QP7mJd^Ei>IkKrU9Ed&0WnM@)KA$SaAiA9R3XS+{U!4wTwb=2fjEYoy*?;V zkwjnRCz{ItMfFcR2eRpP_0Ql&e*8M*m@T>|c@h&}9mVe=Rcd@o)I_46f=SP4{5*Tlse2Dvb0wYQMu5|DGT6 zSlqp&yhBX|4EU;i@fxxHgK7m?x1zk1J|63)Qr~z2x=bKlECRGcP2AukMnvC2TI@6O z2oZGufy@&0dxKum;P{UsYN*n)Y-E0U$MUlHPx41WIf_1{%$GskGDOWoD}In9-Cs$U zB3AyhPr4IHmzH;m|04fzP#%sxtfV`Ubf@x8N*YvGAZqs|5^loCnlE0EWi>LgxO*h}hn%D7_~p^sKLN&D`B_SgC;5(j ziyTI_R4mD&E#~OLZNC6&Skx4 zRwF>njCZE_x={5Vxq{{(HCl0=idl#Yt-~!RN}uU$Xg=U^kTS3P@wANeY5Jzeo9N(g`k%V&73=5y5bF1Z`P>?COTs)ad%mwS9vpCvCaIq4f-wcsh z^U~I_ZG_?O2ZJ;&g;t| z@qJw7TKyn{4#sb(6RB6IN=mt>>9~cH7JGQB%J)b7jry;y`^bN={~qhVf=&I~kb=E1 z<-R!O)*Aw^Px%uZ??}OJO}RgVTP7Wnc=~>Oia#B;E%&M|Bnrawf6uQeV{rH5{C#CA z_cnEPJUm7h*olrjFCm(p!^rbvc{C@=^Q1gFI+f>9d2aWforK_X-h&OtAl7k&qS0Rh zJ2#N$uY^RKdgZQ)Fb0i_XrrDu*&r-ZgeUOCECVp1PCV3?4mgkpv;?K)tt?azk z7NUm#&Q_4UHCG1nOf{p@Zgzthgea5wq>aa++42vqe`2T8zU{fuFwa3(caY0RciDI>p&1%tS;k_`VY(Z3H{VYs z?fl`Xbh`tkaQR$L!ctU)H(1jRYH9s)8Sw0$6~>2iD&({z!uUwsPN$$IjrTBqh5&77 zm;@eh)KtJR2_U+lIfp!xIV2yqK{s1J%M?inzMzr|-L_s1ZRee?YBnSLlb=q?^^*FO z`Uq-i&22|{Li6q%Wje)&C_W} zYj2B&Fupou0$srYV>^d-@w1p>@5{%>yU(-T=TqoR+!w~LBW6&O&Sn_DL8*p#TQ$Cb zq)9oh1?qV(WzaCbt_g7I-YkHUM4xGvbb^u=;-V|5QJ9NMR?;PnlFH>vs&f90a-z-a z#>#u9mou%!?#g*;KoPUNr`WfleFQ6|SgLd-=;EEj4f2Qy9o=CVBSJWLwI928mkAer5uQKGrO1Oi_z*yDO>@nTtEDsDo~m6~-?REv9T#9c&a| zuB4*1E?P?YwH{O_%*I&Uv*lnnw>9$d8>z17B{WRG82q`dTk;v%Zv8Ag%Z_yi6TfNY z+3=XRqs8s*t3bfsa>?cccc5V*Z@pWF>kY{(42gvKg<@PLlCQ0=89#*U^Ww*7 zR~YYz9*U;@?@$z}5rno5q6V5l*w-ncLTWg5E(YVeMVQ9h7lG>wqH7d8oxM6!P@9pX zv}wX>y}5As(DrLxV}xt`ZkR^%HX0U7^c;fg*RM4ibecwG^*9=Gnc1vzzX%OM6cHWy zR2Yv*yJj?qMg`b0T_}a*ysEGc(kM)?5ZNYR1%VWRiAP)sd9*ot6&S`wgNcqyo9{b? zyzCT;uZXYMJx*!9KDW*Rnc_W>NE;7e*=Kqc%h5<)&C3EqW7(y>BW=;8(j}8E^|>Yt zzeM|u{WO66T$*Ia(>|;!ETdltkh-4>r8TV#ZJ2^_5E}T+#uc7$KBtAorrS5X^QKW=8r>cJp9yet1 zS^#ca14sXD6{tf(DzHy{r~T;uA&cD1mlIlM6W`jA9HmX?PNC)Tr$7rjXt_$#oN}?e zxfpYUx#&tN)U~E7jF`_kV&*www8r}w?db^7GPXFZcCXBBdYmZ3!NlH|wRGK0KNmP_ z4_rqd)ku(=zwT+=e74<{42+ENr=8Tv{K-%i%lgb_{_LI_f$qRG9hnCcVx@T2+&t99jfS1K4$aNrk9a!YM03VOS zCf@Vq9c*}CgttGsMmef~W;4g9^PHmFsywB~n&jEn%oEd3xp{_1d1Cr0*Vpi2`>8dL zW#rLZcQ_r#_K_Z2cSxk)X5{&)>~Ym!&faUk%hwZv6M&{P_0Hp$1EH1b8SH>6(@(n1_H$`xjod!7f;Qj%-#wE1r*qG(~mJ0|&f z7*BL9!JhTMur`s)oJrcRijIwQnc$HB6%qKoiFiv9_Vocm*jGf4?m7>e4~W%Dk>@Qo zJCJzzqbWQLCfZ)otg6t4SJ%Ijy10>izovX8%h;dDsVdOp!s%O{$zDh|q~j__3}Ji% zJBwsfF|J*PZiQ5$JB&qk%y*&fjz+`-kBze)d_m3b2nID_3*&Q?cn1<|xxv;cYOJW` zaw}@JQJsr4=$(8j^j5Y(CAEA3nMN1GzNbX+Q={pF{}*Yx^nZhqyFSd z>Mw8aF5HvQ_2hKA?)7vHc)Eg`K`JqIZwqNO2WWSw2{cIM&_5y7ZDu(@_kb|wz}#2y zmzrc>U}W#=$Ts{8FGlvV2HA2o_OYQYJut>ZKSAEH)8#Gwq_@O-FmC#yi%0j-ic85P z6X^8l?KCmB8h0$TJ|AWdqmJ%PcyU-OPWqle1x0*@% z^lsqAb`NX#3K@@%WJjayRiitGM~&vfqpmuW%UGo_$9=Q$=rqh2Wh>!P@i8hkFerh1jtxzr_C zOzcJtQ=YcW)1d2q=yE6i8u0D-oJV);B$i#9<{LW%niJ#}()b04-XrXAC%RD%=~VD_ zJkMqh=bYdH)uo+o+q8+Cw>#(#qnn9V)JeZpsFpKkcZ*F-wsr%gv1w`Jb@eBhmCf-_ zl)}sK_z_B(M~y1S7=IHu43M*fsGfYGr!cptIJbwJUA!pO(c8n>de^FxS?iYb9Ka6i zQYR;^wQKcylI}9FtjchlzkvfS%@lKA+R}uFIZ1Yb%Ll!KSB!EM%M%K?q|qRuT%5bI zodfg^3moZ?vs_%~qIU3BR5?DCTn2yj>#doUOYoiE@Xc+abf{Np3Gbu$bnCcVSB{@g z8a}7-;h9+-*E{^(W6HY6WsS{MoR4ZhSE}qhfX`*^q9*BG+{KO1a%r7QlSC-Ddw@&L z>s2met_-@{6=+Sw7%|I@bD^$_t>P%$UGsX~z2Z`=c9%RQ?WX5jsVDlE*Co-&iB^S; zn)h2%`~VPl{9I=YLU%LXUE_|ap7hnfOK5QRVFN z4i-&E+d8Fhmrm>2PEBLQZEbmMr^d$smgO)=WQk8HP(M zXS2TCgSv5dJ$~KF=MiL)K3%>T-9n`sduC^?ygY$n<6oXG_Z{c}{Fi>Mr3WGGvUN|> zx%Shy@kd=}*7Nt$ZG`6<&!92j*va@3|CL|v#6NOQFtOxi&2oAwuJ|V_N52LPLYZ*O z#(#yn=zWxer7w_84;7iTKoksNfcptUA+;*9O*22Zk?gP`z1 zc+#dp=q3`R)|<%hvXf^qzvzJV4DGbqY!eba-0VA2Eemt;p2=AL9PmA)cAm_ZY(1g{ zi&FDtcfe-R`kjs$W@jf)r7ih$1_P^?x(PITq?ys9K$+TBDI@ZbuE9QhzpK0qotk63 z9DZnR&n3v#u8j)Y-0#DnLcAIbOZ{s9A?|$#HvfFD1&_O%^ItmNIGny1??sx@$Q+&} z!-1vxNaM=VoVzIW_>^lj{vJ&=vtTc$f2clyoFGH~Ze4$0E1$KlH)WwMtsyJq=jkh2-KJ!kW4N8Zn_`f73wH$6eY<7d(i zI*V7G*|)ODJI;CxXr#Tc=@$ypOPG3qY(=r0-P}+OR6~h+NyV_gnPJ}w-IHUUbQ2D5 zm1z_O<%&Cfp`py390)zK4_XN#3bB1OmTExt;YsY)ujF%F@JUf$A6=%lHr zm3Tif%vyhY-in=fr5^I7)>owt0`7ELwTq}Q{*u`H8?`o(*V$N3VKFrRg2JRMl}b@` z^Vs*MROE}PNQq%mHECtY#cQo5cc&)rYSyIIUy@HgV&!HGsI`{P&bF&i#W~gv`gjW5 z%6<_Ld->G1Cee4M&?1#CibK9=K3Y+~ucGGT1B{fFh)tFXgBlf`T6J(Yv^<*7^7bjT zw6rGInA)ea6|BEv01e~cXc(Qv*kc@Jf$A!|oA)1$7*~cT6zhiwuKo6^Q6+xO`p}d@ zNhz%uA4mx##J1QEt5h)kAS>2xWoq+J!rCZTX!=AypjXj+rL4Z4xRDoQKO`RbLk(mxIaWUU ze@;-lMHDs)EMInO=%2z=6~uoa8;$AG!)Fl4{LuLHUV@0lhN~_ohT-1FCpUVj+quPa z8z!711%WOF?H9rLaFjZ8t}>{s?Jct}7S<(GH?( zF+V93{u1h4TT0DKs5$Q58#gJCc(E>dt6MXw0qWvzQ2QXiur9ZL6r(41LPc_C^q$N< zrCB9do!KLZo-6vJ(=3?27iJEJ_t(SwNg3y68W5V*TKLccjiZOEQMm>h%kBBv1Lcb9T6+9(hbR46F6k&8*`76Re z5@9Af6rn$fFcUqBFqlMW5Jhp{uQJM1w;Abn{vbE`fFO_ZV^(-oBQg}N{xw`eV@d5i z%v{L#N7l|&ad6Tam32HYTk}z)8uua1w$whdJa28OBSgZiMwDA;&%JwJsSr9W1#42{ zrMz3E+`XlgzQpWo1FgvHb+%ihd?=Va3|0?q`UZ=>l;ZD}A=Vj(E95eT#7E#%8IrXl zPW0A)AWj@f5dHiyNt#;>l0oKQ>&swT zH;2j>+MC%wQ9HoY*`x3poA|#XnvI8=59{>FpAy~&>lBcju0BFssiw|iW}OKxVRSY( zZ@koogsC%o)Z}rLxF;)LZrqFw^|ZAeRYAZZkja{uz^KkEcVWadA#}*ehzJ&T4 zTTtoug|4y>rFM zPaJ5Fa$a{ZCXT>U)*BQ?O>v(4#?SUt^^F&9fVg?%1*g4R;Oc4fNPC34S+hZM6G-y$ zxYAH`U(on^N_#XG1CCbreLEi4?nB!TQof9PgkkW2V-G)I5^dB{eO8_d>bq|eCG1i? zNP9wgDlS%5?uoCHXT8mV*O7BQ=iDdAoyXnCm6p@){Iw4ME$FM3#PE4tq zu~a<~Q7CK?}r(*F*s? zDxV115u#Qvl zE)mfo*mCmvDfaKRIQ#vPqY!*s?7XQ1C3o49=VW*gfa zb8FT_u>m=;+aZ~ACr+OL3*FiccEcw`8z3{B3Z~w=DX!|T$A9PeZdOc|o3GY_ zM3s}r5^9Pv%CM&IRh6ybaD|9rZzcMz+5pb1cixgT_sZ4t*U#HJt1b*QZskSeR(k9R zTTZlpS?2I^_?lu|btXfq<+XsRQ}wI0jb(Z!ug@hQn}-6^b27PK^edas|7Sg40u&hcYpdCquz53Slk+-f_a;$zdPV3CC0 zRKTsOXPl7Gk9PfyE2gH#oS>^B#x%)4erPI6@qG8beHyWP3z@D_<7tEle7v`#w+GB?AM zUs~OW39Y4`=Lzm;Zf{iXan7J!c^{cZj2kChc~|-5T<5g{&r21UvbqhF3SRWPr=qd7 ziT?Jf=xMyJj6Ip5RY@6-$vo}aMj3Y!BPi>z|BmSErmr3qdke>;x_WCTLf&}_r&`e% zp)`mciSMJ-hvaUnFKwcjD-<^nuG_kdjnfjgq~V34(I!@C*E;GT_^p2k7=J zmSfg(3@Y_y%D+;N^y#guWPDUGfL1Pz6KJd1Kh(cgg2W{XY9gA^DcTj7?DPwBd;%Fd zbWuNW?Xedq9ErptzCmqFv36oDR@oKU05Ch((pl1CJAV@q$;vFKIo~=OjdMA+n^Zt# zvYzD_aNM?Ukf1n~Jkk^w*|tAEMb&+!U+DsK;|}6?N%`SBf|}S-U91hZ6mMd%jCNL`4P;yy40qAAmF7ZQZeYWG zfGm&W$ODUD)h>amn5`B?u6R~E-Lo9|(di6S2os~T?CXfSui;aw=xi4h)U3fp3yuAj z%Sq%C&&Dq$YHgc_I<2;gmPQil-7T`vEN>4czMhmsb8wa$_?gVFt-OYx zrCB7E+D2GD-bwkb;pYZBpGk$;1@sm7sttr@fAKfM>)KubJW1cm#cJYU3vgh zY%tRGh-;hUhf1VJQp>sBX+8TUS(6woOSMY$z{`lY5*%V$3Wss86~`;Li-U8b#}-q! zJL(8PtsO#M0?xQoge0;7%Wh;Dk$q4dCxPyW-AH^sF$1u z5as%0F>o&N)hpS<(C`le^bub9Weox!?m7lG>A*p+b@HD%vI0y_~o`o_)A!ebYWO}>5(rhaUXT@!L=fkfNMON=Z7x*0KgF~mAB755T6YzrrT zlJx!D0Q+~p#*vKz;S6~$XyTi%_Jf;_cSWNP+b@JnX9i|=ODCXi9fdWP6csB|&?p2zN{1x|c1`wLAlBZ`4IRB{e*9{boJBD2o zVjuH{bb%1w76P=H_XMGz7Np~};!%?AA&%|kk_R<$cOBAmPU2P(pzmr$(3o>6dHspJ zFo8!yuYHA|Qf}b6?aUg4>{1MKuA2|%2e$%P-jyHx4c`?D@`Ha@gr1PtE=nURIx~!9 zD+`Q%nn{^~W+i?tG2r5duk=h#q8KmB#QJlSmb;A>jJUcBCDOp@<8P3^o%mb9-`@Ob zh59c3Bsr0m{(AnN&)<3cUCiI*{9VD{8~Eb_NM_Xy{J?{FFY!Q*y%WbiiDTcyv0vg? zlQ`BUj{OtI0g2Ne$oQlibphM84>SuG)5Q zCOON&)GvST)9GBKnU#Fexh~!F8tJ6yWdB@{+he>C$X~49HP~*v2Xh+;iO`gLLnED= zN3~E<)9xQX$7!|opK#Xr3ndUy%eR8j{T6s-xy~>s8hi3?b&%GE`@Z9;lUw|?nfEtn z8ZPf&r}DN|n~Q&qa$I{Qze_o9ODZ}?-c4n2bLcfqZd!E8r2cgPU(@ULOqyC}ebia? zvfM7SbJa`L+s=s2a6H?XLTJ6nt@RQ)i%0i`7M{!J()(cAd#Xz0b3HPccwJ*UYt-#o*J$;H_CW&Dil6)ETaFT8q9*s(Rf9A!7-eKvD1-21zDKUt^5 zbz@6>t5Dn|6iYm(CO%1>4zA5)4w$TQ97~<-35hvlQxfINiZYnVY)GPrcdb$G^H@HY zL=jtkl=w?689w5pu1%tf^R3u^F_k7&ii<+qC}akhhyJ0mc`jT^29wW&{JLr_1-#^o z_y><9db9~Ha7lPIBKdaC_is~d)m<(^R+}5mBf~pTiSv^OY7wPs$Ng-%Rpm5Xiydsu zJzu$93cAT(2#Fd(5Pbn)Le7g7VNa!0qw!5X`l7|K=5J;iq93EKM3WF&*Dze8BZo5^ zIh-LZ2UreYQTm$E;_Cj50ODs5!KX_>p95jO559=P|5Lnj!D7`Et{6|o!v2w21N0+R z|LDH%>sysHH7$bJVC924y9l}Dq0C%N+{!nnqB-E z$~d+x9hVVQ-JqF91&Vzd>l79qA7CU~Yjl8P4B+@D4DsXOb{WSnBTE!N%V^=hT+TKw zXBd?L{tLj^PZ7*w5%(P_Ik)v>qdR_X1Ctx(<5!TTJzrss>od^o(6?@R`@~-vIs9Qw zu7YS#SmIMGy*W4FjJ|Bloa|dL znvB>sM8BGd9F_$&we2`S{p_6RPRqoN<(M$>SzGk06&;bb=))Mo3q@>ofyF4&huyiu z*37`dSz2JTW(hKL>C^h>`?SWJAUcCzr<;JbFKy6OmTD;8FI8Cr$A_mX%VPY{SmbKY z81>@@&jQySj!Xg6jpJve0EYCa6u^)klLDxZ#K)xo*8Yx90n|O?aRb<}>00@6qtpS^ z$>Uv80Auh916XT63nwhIb)BxWKWTkV6LD-$CrEXLTc8UTy74w=ituJ2trMA#WCtu0 zn$cfQbZ&?)tkPZ7W%Kdj%-l1^-@p*belI9+l*wWM^QY8C%Ef}>ATVfQ@icue4nj@a z6IWf6oW#`?&U(RuiFUA$eu%!lO#AcIKd#OAe=9l?_ixth(LQl&<7L|E_=OPshK~zr z^+TQiuoQmn+IUfg_1XzQOT1|jMuVJWo_`8wd>z3hf)?@-=AK5-OI`U$Lf1^`cGzAz zR857;o@iu$_e#2N8rLkN;lx%NPIWX03Js^vM8ni{M#Hw!L4$B5G`vbQ!1rT;*{QWT z034S9%wcV7K5Pn%#-j|woTVt<(Q;Wm3E)<=a#1L)WfecP_(wP557SnOFMx|LS1Nwi z;vdt9|05S)02f~_5&SHDb1d=(Ug> zM3?hxZ&ru#RdjvR0Yeb>3f$2fEQa_K#y0_&yb@;Azi3Dt^i_ghCFq?g=oNy#M$m5o zb;$Zw_BdA0n|!T89w3A}@5br*O^^dryWIHyJwvKlA&lQIq_bFp)%Rgm;MO*NbQkO! zW05}>^>a6RY_}j%{Gpm+X{~oPxs7p4f9u@OvD+_g#_+*=6FhQs7{65n4!MZ)rX{U7wLFWw%R9r=s`GjHTjmTe7iK z^m+d>x{EwidpjH&WI>SGP?t0GkI=5}Vs)8|+x3p@MT7k8$bN?FHP%L0XMJ6vcktsH z$Cnx{j<9GYM=KjEMdue0i(1QU8Am?=Is!?jouA3 zx|yGJ$WlTuSMnafnEeW(>u~gMh&Im@t*15Gz_e&n5%Y4(EkaxfI8?h4SE>PFLQPeF z4#!-s77wuI`#Qy+aIIjdrg6p|<$b;Sbw_t$>)abyY?lio-8|6y27rT4B0gR}byV73 z$~it|b#Oy7*>G+QH3bcG+e_NWl-7(`p)nNN{O5EKu&2U3+c!d)Wa@`tF63I=9az;e zMz7qp5O?G1ELN_rE?>E%*mrgHg)0{p=UrXBVCCH6{Hv>HuAE)$zq)$b%6xIb)jF{C z*9AW+{nH?`lsOswJo0#x&toomT!%ZpjoJ@aHd8$2&T*Y>fjo4DjWoNX{HZk1rBv0P zl~ryh-j1^nf0o~POf(|ijoz#JXyUvx+jm^!9sx4rOhM(1zD{&qf}PFq!~<$U^fmx1 zvwiJbI=ArDSGaUbCRD=wu=G~+uUgH7duUXUdvPX2!anOm957#@R?-_?#mA5ObN#44 z!Kgp6D9;;a9xk|pax2+!@x<|t|ec# zb@Au;#=3+$7WSez)AZLbqG&$yD;0Sk@7exDJjYMO)jzLq&cq|f`n0M*4#^w|R|@Ou zr-XH;>76B|>X41BKJh;&UtJqtUQRb*W&C;YBWSQlwk6JuyGxPPg%-o)xb5(zU3_sg zSxNU!6SzF(i5Jn#r1y*tP_D8|e(*B*JTfacnPnGUw-(}K+a`Nq!fg5C9Z1^}(O7Q_ zp2I!go2R!#-Sb6E^o{@f34=AUmtYeH02k}Z}b0JFtXQrB8>As!3da%uf&S74<` zpCFpS18R$Yqj&Q({?=0M|JAvfIJoxh-s?4;J+6}r=^6<7E@ z+u^j-+0d1BbTMb`Md3wFqU~648C1CdFU^0@GF}?m&al|#t{)V;LO)H(n3l$s2)uK^ zi*@!?PmA`1SAZPL#l0|E1!yTFsI`q=x2rmaJC6jSi9h*MBv*ACP8maUnNNVXOYP4h z=-z!zfoKPJy{;icGgLAv@O)tY)~HD~AVw|GS|AqNLy)lw&1hWIq0h)DqlO|xUnlpG z1H7*i-7oJ-@9lHm9fOH)gFZ1Gk-~h%Vh&JUEp3^N5avQ*bOZXZ8-w@EOuQ#ETVwFa zj{b;lHB#-Wa4+_83cQptC}%c-%9M3-ZzR=2Cf>_Nozy{T$Zkn6R}f&j@-7?ZE|^fi!D#0B)>zw+%C4_!5A$v3c`bz_PCmx3`Ce1o9&yf|5UeJdyPbX0Ye z$(!iRokgFlM*R3jAjSAS>f}AyB;=ihbZ5KUx^vz6?n3+#La`%iB=Ss4GNHVcSn9`x z=-asFRVrMkH-3!lTrM>4@v2~$<9rNG4VCltA+{Bl=*P*W>p#O}MQ&+6zCxL>82Ku= z((hT(M7@0S@XU?Ba`UIwR>uHm)t&G~tHGM~9n#RTts)a=q}?IL9&9vKjOb8Cxzrw| z8FGozy7=l7ZJArZk7hxF|Lvq6T^o8_@+E9|Gwz|W1q^4Fje??k4VGUTMBkLFbaXDd z6T|TdDN16~{@4<3MQGru??2y}R86xVp}7^G=yQ5`Torq}po= z!|Z+vW#opE-@9719oqV_g(>gA`o`kv9gsY>e`4Lj$5H3Y=i`Nt70qG%z^YM+4C%wJsdi$Vu1A|XLiXKvQy}n#xJ}#G1rHH-; zMc(fik#z9vDB9B+7z)t?#Hp4YI^pzV>XuF{DcU{cUqUaM8vENl0p!xOtDg|>asE1S z>z{Opr3J*jE#{hUfw45e63eNh4o&V31{;uvAo!pI>~`KzU>ts--Hpq+D@?ZT7oJ{? zx8C}OHM3H#Z}LW1%Gz2sH#+i#3Ywe7r}S-UXFnpjLtn}C`t`oXlS4!(|5EeA(BLu0 z9(1fDXHLc{#?22H3Dsz6^G3EgZ@6j0C~$X|bVo)1JdbURniKM{scU@lPakr6l4(OD zj*TZH9c;@q!lXx+Ht;aO^c~}eqMI7yNs##*dCrALbAsq`&2Txhj6{WGiu{%NlQen< z{v@?e>uAlSpEVq~v#R01ok$G_erX>pa!dSRz<)8jt6jk`FP0^a_QcVVI64!@?8GrA zadah)vU409^jEtn#1j-E`o1t<6NHU(kLY_q94iAZU7w9FzEF`?1Yy`Wo^>hI_EZnN zS6=o>e}iVa|JN2R#~w_yGe2G7rkaNK>q_>`Ue}&O8@ug<@s8=q3e9942?d48SQK0= z3Xb9Ml<^b^K1YiyT zZxH}4X?|Mkx#cx|T7;1i9o`8ev!eqD%9XAqLdc@fMNr0X724)E`mNZdJO;_5qJCX_ z8&QVFD$)~D?7qogii@SX;O^K%E+KiR|6cFEH~R0*`rgsd!Vwluy_5?1d_zcDzd?Y> zo9Q_9cPItp%PboQ`b04jmf+aXhFgLZWBkAljz4LbMT06eL_6ZAx3?XoK1c(KOx)(X5m} zRL`8U*Yr)RU=05>aOL_r<#L)*YG*0MDf2cJr_@5c;*`b<-3dwQO{tl=#uiLW(}Pl3 zrYbpEf{AHLP9oE5Flq0Q%^=Or%(FJHrf5QYCysvyVnenMH3LiDY(mh6Q$o?#)Bv{;FRKBPw{NSUSf>h zBVbwR3#4OcF5c(zg>{{U_F`p^uCSPD%SVsMu%b&^ z$;Sv_a+9B?*uxWhG}dl6c+BE^%4t=As-Z;C@t_2p)M1cW17G?n|6EpN&FXTM((5Y) z2D4=IMfS}7ahkarC)hU!nV!tx$|i3Q1NQ>st+m&m(T2@dV;f9Onnx}kP6|Q+wd23(THi|%n(%7FS#HCE~_z|x3 zty?b}-`V)TVRf=!Y;IUJR%hELf1(T2w-Uq2gExYAk$BZCQEB9G_8l6gG2o^GA)!~1 z!rfOOB(lQUNrLMjR=Dxq`tmh(f%ua!(8pRg-nnG~n5@+Ar_@|eZk9S6mWqzAzmCka zpXqpj9J}wS5v#;u$sa|gldY1F0pI{al zb(m5klh)|U$q#{9FDw_Ff@yhn(K}=bSn7_2_>1&eFR$%s8|f-zX|@?4Ym<$^u4m3j zU-ujv8+wRW?sA=8u>o60f0T|sC*9Hg+-8c5WzI~?PLZ*j8JAy_s>_$L`@0m__rPdV0dCdEN7jHSr+x6K-)Qxe|)~ zZaeczqR7CHw<+O49M#JNW5UT>rgKtNnBa-?)EK4fGqV)k7y@~!t)tvV&CgTKV_UBj z-GvJ_<&bwp9db|8Qndwz&J5_wPeDu7qF=4(?1!3%eeS5w0A=|6F3J+EV20(PZ&)L* zm4qVAH<3xA)LQ23dac$wuMcf4juK6E-QG@?(N=D z&hEk0$He2Jjdz%drd)g*SXR6zEV!)J9oIK}`qVn#$l;GW_10c4$Vg5#XzLuCT%<-; z3ZfE~JX+#DpcFVtBZZs^>^2Z$HK%I=7W_yfc#vJWE>|u<_VukC(jdcWXS=CZXNp5r z7Q5%jfZ^28dnj_eqlI!pk_y$*x#CvJE-{7;Qo+rMYseDY;pkZ?Q~OAo{o_mNH|JpY z*B9YZiqO->Eizeb(WUe+5~(MLz|?dPNo)L&vK=R{x#+X7HZc`u!~X56<>vNe+}Y|9 zrZ1`4cEdq=zMS^l(eMn}m@@AyXE8<*Th(bWd(jRCvUaL8F6+EeJFQBWxhh@#s#)lS zPRA@2tZ25UM>Sj+wCCQm=f1S(n`zJe@)(byp{GFup6kZ z;{m1Y@^pMlp0f9RTOOmHXx~YD9!z^4N_)O5Pg2+4OFXMS{eO2&B+T#qe?Rlhp2Fwo zJ+l2$fnFMUA^JQ3Q$B5}8+&T-3yPa`NNKc^pcs8o38dQET7SlXr2Z0*)nDSV`b#`B z*Pq|1^o7N0SJH$XESklf40tdB_a=~C5{NtLhyoY!+$LDVkc(aL{kXY)ERF2lU2^Nk zcHeoby-Ct?dvg!zx~uD64dO^@Ys}BE9w=>VjoC4BAnSI!EW?8%93?*G#Xx8LCHNd) zLR~EdgzS%5AhBn(<8Azx#hE7O!XPL;$IbT-0?hagCa^&QzdpIEM4l!J2B8T6G zPin?v*o1DGG%TK!jBRePc-E_CjCZH-eLKNtJ!-~yceTXR!FnU#WD2l+b<*yNbp!N$ zMI}k_wZyxd5O?Ti1hzy>W(Py4i1Bwtpj(%W!>7esL%0=(wW1b9@?=2px=~J*JXxSv zblq9%KvBZf1N1cWXj`uO>-EF3jx>C@Zy!>x?^_wn8=mjvJ})ERC0YZHg_X00Q`?c! z$g$gy{_MSN>Cd2Bf}~_X=Zq&(pmPudElk8fH`1M2#6V=8k^^;THcU7<(0s8i^x^y9m<9GNO7o%g>E0w*JDebOuZ}05d@?Odr9C=X>QR}$AK`4IOaJE@STm${ zV#bUA#SO4<}#9q zP04$KQloNu#n}w1!@p0i12y&X;>=Jb|(=@Dhp?Ne5J7vDF zG%mbRl7Y~xP2`&z6UmJ?_8sXIFL$M?hRLP3!t_gTBuPeW-Ft&3<-A`|dpB%|zsles zKWB9B!>{lA^*tQ3_k4red-yf+J{z9NuMxYhbPXGy3iji#oQB#<+s~Bct=#6aL5P`mgu_$aGS&NKW6n3!j zHVQ>kZXIfc&L*O4J{F87>-CBC#eN4HHv@@A-p3&iv3`oY#@WQvH#quM@T33)#P^XO z)x|ByMqE$4^L`5iew zV$KKU{HQq}lJjHc{H~lIH|O`{{DhnsW0fZ8?A+{pv@_jMrsxqGX5-J%K%+Cz33RlE z_D;}F#z!{bS=}vInagjcBS-E{gxb;UNw|zW_XFr2GG2Y3Jea;z5N_u_jL#`(>OC6a zlpf$sVJ0)&z==)b3RZ@43r_4rZ?CVXt*-|KzIOM-d%vvX&k7@a~Pq+yyndx^0&)4TRW{AZtVW@ou$HG>7jb=ZrllD;GO z$r|B^;17oHyq$)$w03MK@}=3el>IlerH7u-&gzq{N$x_=oX>Fm&XMh;D1LvpMb*SmEtvcjO;8*hPCNc^6` z*ylCAYH9-IZap+dSh-Ak9?>foSKLUJ7b?pxUfI*Ly2^6ys_#*9o`#zna~Gu4gIIC( z7X@ik#%(=)-KaJ4k5}|wLP^R9@r;aTK}LCA{HV)*B9*V z_=f;`=8rt1dw%pIIs41~l(3+T3Ox(T=qelPXRCw@x6b{TO1LoovCHC3sVvl-%l+jA z@lOZ?+fVt7y@y^ke{1CnjZR;l3*x!ux#i-Th0I`-{whizJ)dT}*IOx~&+ZhJq_dox z+)Vl6$3)nopvG!5%iDm9tS$PaJp<)|c%84SDs?qbuEr zD#_xlbD=3HFOGlaO7fXhN!0JkgXJCK#|bM}Nnb5jeO~l|I+eOs8xUH$@eTPFZ0x%E^0Bp%=_y> zIf{SbsC_g=t%ip3((2IuUcXM~b;k6(es|r}a+T;v|6X3|C(7$L>AYSxJ+H?%PtEI@Jj(sA^I|Y=)TibZ z<@MWCeWt@T*(m&Xsql0is3$1=_i0+EylSrc{_&fq(t1kUQ=v7Tm)f!N`a?ReDX*C; zuZP|;HLuV7+kDX+s=WS~DtDSMnphS7r&M?@b0>TqA)2-N2jAM#>Sd8H-FrqyWnEy zQFAssis2$3{{<{8lMH7r{wuzqFL{Vqj>bF0Iob2ij3#9|{cY3Al;)H#(<_M8EYq7& z$ky()*|F_OEtSdAR^q=UW%?XZc?(Wz-EXC2(fgWopJjCa-O+97a`8VzcXdHRxVb## zT6?u+u7ving!Z3Jr#;>JHG&vp?~s|yi&Bie3xH#67~ieHw~4k_HECOCwEff3W;k>4 zR-=s$mLtvKwRl*)9d zajW=F0ufj_O`7DKT9Pkx)Duxu_)Mo1K(tbT*kclVL$3>n>-((v>mnpLN@wmRK7;^ylc>*v8fL|m4a{zcE z0hj~8FB5<{0Q@Qem;=DC6M#7Y{3ZdI1Hg|3;QLq-U=9F}_<%?K_lN%bBmaF^UlqU{ zE1W@+NJi0I8+tlC(!XS%ZLbT-Xi0qwDQPGAT=G?Z1~Y?fHJ?N8sw$x8AS;LTT@!d&WTHJ3VB&DEL*t9dtA$gsJ4rf%VWOP$G6Sw95bpZV>uUQ%R9ud_cQcd*gnf{F#q4&-Ry>Dj3-<&b>{em{@ zEtfi%%}qa+{VB1NV_8qx1Ayj5ol^(d~aaJ{}MqC5pw}t!v*#_hqjm>=jf1BBUn_&dE zHj^+b)nwKtO{OJmN{eX;a}9=;I0Me7)?RobcO4^;Kh}(nPPvb5xcyno#Dugv+$=IBC+DDkE*s75duFbKNz>M?EBQ*M zLMED`{6W^&kc~6+YfLePu@b@ZxL|iMg^_tThJTTOrU6$D86wSRW?D>#3~v3mKlyNx zkaedThH2+WL}0dV&|5f<6wGO}%+3v`j+*6X)`&XTO(>(e!GOtRxVm`w9Mey-TLeS* z{-bf>N-G;p(=Py(mTSM2U#l;30Qg-3Fb5|P`c?;ZJ6z`x=38sN1DwzDF#slnrSIyI zv6wOxT4!BBIo*8=EiEWX7oS|4yDQ<&R62sF46rkF&;Lc|L*T^W*Zcdwc3-hF5O1Lh zP^a?RMDAN~pQ8JC9h}C;CZ$TUj<^7<7BA0*yV~CaKpu@@aM26%UGt2d6lHy68A&Ql1E_7(0_`f69`>V%V@mMYQ|Bzfy)uR&hrStj^ z$@Tv3^O~2=>pvvdQv*`Dwk6k7t5W#3|T09)f%8&14-48c>kfAKY7Lrs`BsRYBrb5M8CQ)pS%JVldu=t~@z=scF3C zh&jelNb3ooX@^HwkgCk92Y$R^&ons*P7dT!@#7(8GTtG6I$O0Jw^{xWy<Dbe-gz zd|@+BwqTw>gK{w*2F)0gzWH>-RkCU@!#tQuSaGkLj{@(n~1T+5{3 zSA?h#?`()Dl%54MM02JgIyQx<*gz!lDoT-a9}=Qcyo({CV06YJh=w->(QE17SlqJT zYwY)R_WOE%U44gkRyRh2_jSU#dkA9g6(S~;E6ti_9rf{a4}u}FJ|7<_n~JwVwy&H$ zev+!bcvlGX>x&C%qb@J2JMzjl@{%i^SF8H+YB^VgM)^Ag9f?MG&CDviUud% zzfClJwUvhF7!4nufd-|CcW=-zC!5Kvw(;OCL=NV8^$i3fQt7}T&PbwkVfqrYoW4YU zy1InqhD6oG8}9-7UGMDc7#X2VHUdb?p*!3<_apR_;&f#P8w35yd{37c>p45?5}GA9 zO@=nF$uB7tmt?MMA!*5ahiIL4LXlL87%6)HKt_t38T>EKqqW z>|DR-&-IJb&-Kd`LsN55(~2P8%TNT-TgBMuZT9wwjGU(j#EaRdZD2-d6_ag5EmI`-jQrx6j>8>YICKG zuCPnR+h1Rb5jfuXz8)yU{nWmK*m0etc;%IHX3l<0(JMnsl=k8ci7C!CC2 z#XbX%vglS8s%kD(J`)(qMcs`oo~JB&YDWQ|yg^wUjf*T^-pqo?Qcm(%k0G2ak_PdR zZxGQ9DqHkU`@PYAZ_@9)B`fXUkCKMdEIrY$Qv6D2{VmPbH6-ynkPkaLA zdTWW%*(?lgDQE}9`DhcB;Az!RCY`eQAar6<^p~@)BRvZW{Q`w`*&DV~SSs|cMuoch zJfB$&-!R>m@J-z(K0e`_I$5kS&gW~PM&CtlA(Kmk`PNE0 zeVe#>wRT$F<8ZVwreeK(< zFAcKsNlAU#@T6JQBeKwollp3e`ugg$@j}pI{2bMnhHBLpyvFLJo1<+|_VvZ3PAiX; z5IJ!-cO#3w@up5<>%h|38b%i|uyn{~-}AY$&Cp^?3d~j}L(9j+_%_o3nliK$qPI0` zRm%(vTQYSuYt?3(HaF3nOgWY&%M}dEt++b1;FwA`JicQF9`D!Wv1XZgJvgmTXfCp4 zheu?gmnS@KgtqWl&|oReO_%E)JSU>s`GvZ=N-HaNEeN<&|QYM}xj%HO6dSw`NAt#O0(I?2)HPF;dE7 z6DL4rb3SXC9$Q;6J6UE`r!VbGE15yjbi?Z^iYrjx%JzwK8b$Yo)E(>j94xM-{FA4; zqDTw8Q}1mbMh7pJvzxW!UX7e7Q{kw^XeS%q07ss+sHsv*@BzeHpi_s1r9m=C3&ST7_u?4vLV&2)MIyK0Y=Qy;_6I#E-%&qvmk!C8uZ0Jn| z7_?oWEwZRI4Pf-ag|^&4i@LT$s|Hs#o$QYIbfNVNO3)_Vzle_S`*}**H=HwwjsD!d^L45#1g0H6%IaZ=PKYlA0Ubd__zWl6fo%nRB{47p@8!hu+ayI4+MNt0T(FXLIpsN)(hxNWw5D< z{H>2}rdG?T;;PGm7K)o?X;nfpV!MXG75#F~T|>yZYY4fTT|>y# zbPXYwpLPjBBfTym$UK>DAy^+kfd;Rdv^8fgOK8%<%IBK%nNuZ)%iA5413axG=Q$qb z;}=0$BIncMDjy%i=*``yV(nxIDAv~7?*{ul#ePraciP0CFWQoCrFCq?SRUc@K6}=J=(h5~gyfP-D$>(C_ zb16E+%MVU@N_zKRjL#Y03$+^2B3%*i6`&cr=&lP;MY=8^W#uaWVWRP% zCXJeo;!7NjKb=OST1?)yXQHt#8h?!Lv1n{~rqQ@bG-^8YG%AatyCeXOx+E}#M(dw8 zR~n7E3zN>IQk!{C65j| zJjqH;5?}|($CtvG5yld0913VCE*nwXR$RuoXC;zM%;b2FNJ6cU|S3IZzxWytiqC%RWf@A$;U5F zm$gN;+DO};YPFSB@>nZtHuG`Hx;-81fBLfO6tq!R6B9Se`dc)vV+nd&%i5}lt>P$R zQXGkY9V8#WBwZW@#^x=8Ik*rWavj zsWhC+FE4gy^3LN{hCY{95R9Vj5os%Qj&%(|WOJ)lw~CSlC>>B@83uY0*-kBwB;kBaBx4 z&Pc1)iawVs6IuoHC3&Y9+q$YltD$xod*S*W#=T7))L*XS2HmHO@PG2;T5lW7T+blnsEpqXRqZ2n)G*(I3)={l_^mix!JX3Kl%g82 zVBA7{31^L2*^sTm;FEK(X1BpzEg-~-{IKJBs>qIW>&=pgG#JVJZREMvg>27=jH!c% z8~lL*uKepW%U+(8e`)5*4?Fkfpr&@j<*R#`c9T0=YBjy#WEUDe@!P>XyzOXVHWkh3 zx10m-mP=2;-Vi}$30cj>)h^REEd`Za1$k!F2{5-Px9IBXob}bU8@~CL+(2&N>gt;H zCO*R-<|eNv>ld?%nw1>{7Se|2@T1`i8-i;2>H&SauHLZWIQmpr?}Bdn zLYdF-V$_*sA6)$=%q*wh9A@j6w&+eOx-J`SG(~-!ifLUY+_-7h>ppYcr{X>`Oh=e* zPGHyh9nck{{Z%ThQaix_I_{yGa(B4eohNiE%R@C0MY|6tLJQ+{eacybh-M)Apy;eSb0QyzJ|1^| z+=`tu4X?dcs{LR`h9!@wc&Y7hcR`v)4=izZXZ8qc3rX(Z;X2)qP?H;3hS%o{Z&>f= zGyLtKwt#QAd?cs|9$!EV0tE~(Y}BjkgwJHgt_vNk9pK~Vb=lK)x~&3Y)XB1RZ!zCP z-Gx~Ht}Rl0hgVdhBkKmccVoJ2vqeu)kY zJ4P1g3-PdoDGqlr7rzM9n8(Z#VZ3DLkk}(4#)@wRxw^<;EH>(OcS_^U;j}aE0!0r% zD^Dp#57{^RHlKF7Cx%i!ite3balXOhLZ-Xl^+~nYktUvO^sT3c$kkOb)4d?UGc!Hk z``@DHUm%)F&lc*xDt=uu$nVywHxHN_S{DAD9VvM$NB6u9*7q${O(cfVi5T}Do&q~bFt?w2 zEX-t%pq@i1Y%0aXPM0^Dobb$Zrqf(mqn-5{brSQ3XU-HejI)RkI*U&`n5sVt(K;ut z|D(SJq|@KN{_ixtSMaCfX5qZnf8|y_U-f>aQQTkS*Pp`vpZLSPYNPz={6*iYBM5XA z<@jY>UUl?LP;S~YGUTlLt=f6!80R0Lo+zEq=)3faRfTOtJ4DeARkZIAZRx5<;m zI-`-N&lH~r-*^nUk#|c1?1x$3U3BvdJ<@mGUFf<_CsuQY=B029Gw+t}TNfqiDz0SA zGd6t0-+;Cf#*3ePvbqOyP>cP%Bo{8$dQRBb_FnK_z*~8tw0`W*^#XMODnwfZ+AZ9;L7+}R#pn@%(4Mud z?@Y2zc)1Aql=ZSH{J@SR?dnJy0~5-gD&5m?hjE!~xCk3+^7YIK^naDcb6aWj zv7E+2EeSoM- zZC#SfoMA1Cz8%&@5D7$25b2(|l};(f{ts_&0w!ls?f-Y)?&Iy>Iv1lNr?Cz4t%QGyR@ARdwprsr^*dDPH~(f%3VOTKs}=P!;Ko+Y=Y7PLb;~ z6!P`1joB`OF~)+bF;a5(ior`F%4ko$zs=I4EZm_GEUf>7w6(DQ{Zuw2Z6nH3?O6i3 zrC z46jaySMe>NAN~?wEK}~54(|e5mfJM4G*|fpgAYm#4kx12xSqA6j(V6Y>u$j5qo>W_ zH?FDFjtv+=pf45Jmhe5e=8ub4fQtd-MZd+3>@J zvcb>!SEklo>(?NX*RJH{5aq>a)0Hc|Hd5p^%i8vQX?to9M>V^$`Ac)Hmr4__-r6?9 zb>HO|z0sWeMCcr9GEsgaVwS5q#oo2;MCo1I;i-pgucGf7UhGM))q#`|EZvj9S`t{R zZhNitVWl0OCLWR2T3VUZt)z9>^zu)KKZO4gtMV(5xR#%{AP%0Vt^OUxsMIRlGR#n(6yq%w>s;wk+J6Wy?$XAMX8?0)iv+g612&y~b=a2m&RUF!F#_m07{uETVSL*Ig%ZSM;sH&k27OXuW z61hB4N)zRv+;$^b07Yavolo&S!t!d3zLNt40?b&*u&nILI)=xMrh9eAz&C2-DF)nK zq~A9D3TpG+K(x?tuzD3y-)vXfeNC6pw&D0q-pWs`(2caeD(+ga+WRy~40mIOR0ZtK zl=yU+d3N53{;`&C$Yo)tWXof2bOk@ki;E~gso55ojN>?~D{p!zm#Pg_1J7ES({*mq7 z2{SnFYn5ZAk=jF!l80X;_=45^ZkhW%D$w8sdl~#YgZDQ0-4x8=1uw3L(dcCsmSE^* zV3n(b^*x;X=p;ItE&qTfGgSVmex*B>y!T-dslT9l5MeFJ@GKhD2sOWfSr3MWcpyP` zooma^!vXE!{HhjnA1U|INOaX7Z3U-Ozjm;B)lbcRirmM`|C{F4*+x6$x$1FquaW!X z_)CRmj!!7?E#`TwJU$9+l@#=6Rw#AB#NA@fms2;SlpAc|IO_n&Y$b{HqD_=z>p0I`3JFaX2>F~R_{BU_)tSpfcm(&T}h2CICVWNL>HGU)Y)?+9_I5c53Z zDIpFMV!lUwR|wV9pwA<|C&Up#Ebxf$3!$1DEcA${g*ZxxMIP~t5UR_;VvqQN5XT77 z-|8_x6h<``>w6yaBVpDWrp;r1EQ~5{(BJMc&kCd38uWK~%uj?-6%G13J?5vvs9pyB zvpwc#!l*K`k>D{u7e+OZ9R`p2g)lk+!rp_&{8AWIL^dTn=2ya~Nn&@-W1bU62fc&- zxgPUtVN^qd{-_N8Ll`w&-m~&y{!K^JNj!wh=N--S8HFv~pVAHtk#nB^YBW^kc) zo?$|dVav5pd#z!%^O#f~jGAB2Kjbm9gi*7@q@l+I!kllI5syg=qt+Po@8B^RVJfhjV8>N^s!MZ{40Y>P2EJ$Kk<%B(0mP+f~uOhE$P&>ua|9JHRj%+UtEpZ;NT;GEdi`KO*DBOBftX0CNdmE4 zR#OCG8l@%(M6ao)2gLeUO%8}2PE8Gnu3AkDh*42Z3kXG-niLSmx7Cz@dV;E&5YSxT zxdV|WH5nk*scI@f2_Q12rT|3F)C7RYnyNk!c~g}K;wHMPI?w=* z!g&bIn5s6orM$G~p>22##zWikOoE37d7j5Z%Xo6bL(6%I$3r2H+Iwg_KTJT6;a^n} zh+Q&OMW7Ks1^|urk50BQjjOa%4{(q5k2Sfs@87=3y+cq{RVCz(K~+^0h#d}9O(3SV zs**rE1yxl=AYL?86$D~Brm6?DGN`J`U0-`6zkWWmm@(r>n`aBBGrjHD8E_j}g?haX zi?si={a4MAn8Vo=X?;+A3C>Zj6b>*|@~%F=CCjvGQ8cfEw$3?Y{vmagrJ+c=r4lYs ztf+GD0dk(1ysPb_G$QnifX(DxZC_!l@>_gLP?hWC0rXR;9gu)zY(p%@PrOJd90&r|e88NpDDD3hrfv5I7G2aBuk=;3~ZB|8f>w%jHnXiT8gd3U7 z0SX_an7RDw;XKZ~pjeepwDGS&U&FT_y#0usr)gPR-V+aN%Wfy=Ny$(3mhg)$S7Nd1 zO80hTAFKGi4EAPch%yM5BkMx%>1{lMa}-M5iF_p8ZL+zOM&@N$&WUV3 zBAZShlsoyT;X)=~x_UUHUnvoCy{EM0`}Z7wjCVTH4x^TZbAHYozoz1Y?{wKIG3xrmYm*p_0pWE?j0S`7 z`Xol9LU=C%(RdNwl*DM<2yaeeG=7A)BrzIC!Vf1g8c)JolNgOF z;YX4fjW6MCNsPvs@b)A|<4t%+5~Fb^yfcZ>_!Hih#AqA}?@nS~Da<`d%&EfMo5W~* z3hzr|G){#dO=2`&h4&{h8n?m+k{FF&;m48~jbq`*lNgO>;U|(9jceg2lNgO};ir-q zjdS6HNsPw3@Y6|*#=Y>NBu3+3_;3=VaWH%&iP3l%KAOa6TnryeVl+O6pGjggPKJ*s zF`CMSpG{)ar-z?QVl;k+pHE^ml?%U+#AtjBpGaafPKIAhVl-ZcUrJ&$ZiZh@Vl;k+ zUrAy#j)q@NVlnVP{h`M9A3g$;`F(_wYQy z2Y9$ouytIG?wJ?H_7qyvz>$1*U<2Lj$-EbIU=(t1@Vz#cM!HVkf=lskMX_``vDByP z98AAiCGs8BY#xT=d^VhOKI@c|qeC!^4d07&;u*asHzSQV6LI8)XfPd^O;^czjIfiD zIh@VU(54{#4*hjdd5YiKcLj1H4WCMpAD!PS4DB8C7@!07-9ngt{#1B5j{F4G?-8r% zeWmoqkD~4wEyz-lp!^UAbTZ-hMO|mX3Y?{LJ$y%Ux#|zV4n^p!n+rCmiVyUB`t|;# zrSstD^F3Z^h9$y90o{)Y_YRIPE|5eQ{gBtj`yr@3A7vY4bP;F0v_rDug!G^RQn_>W z)OJq}$c^mlz1Qb>na*}W*6+L`H}5#c$MDlc>&RjAJr`}M1l2dgL91JU45XMbSJo}W zncPQt$?NQ@n$?Gu@aOf;k)8Ro5R~69;wahVdr_dyndK=-CB6t!W)d+S?xaj!R@Z+w z;st3f!Yrx}B>naQ^>p3>i)r^J7Gn|Fpenhc-xi+;)1;UVC!oK^L~2I`QoFR3AFA_a z`fde`_1zzaKCjQpf z&^Oza@p|utnvcmySVG^e3wKg@=L3pnCCq1eg9~~C&>OwkE4P0`4Xqy^jLr}4kvieZ z#(t36C%0mY>5M~ZSeSQg?;7_5$d)ugdJ(s599T_`bhb4=@I1kHSkjx{A(LOcmBd5%l_dfS%^~$FT!yZ&$8Q*AoeU1ijd~rgx1L?8I@!w_fw-I=$f!hka%)mi`?-rOy@IB^b30~fa@ZLs*_gMrfu+gt?#G(E%lj0C_ z>SW2E!8jFg-jj0_nU-*GRrd6`ydSEKouJffF~vTKgy7_5W@q^&>Wc?xauI#asl zH*LC-rP5vg|D3MmF;lwcH*LBqsJLhOf6MRwFOaVJ<%U>qOK79fdF!D1PF(fJ0af25 z!`*Zqgzo!gMv!B?QTgb6rTWNp&tbiVY+v{*s&(T#uh|`Bd)J;c(vvR#oX*NW`DC^( z4jXREF5V@VF7NY^)Y*sG++YFqVgL?IZfmjZzs+(-ZXGhog%Hcvv zD5$C#xa|y7(YYZ5)ePLQffB46F;FeQjT)#H5QS9>@L|Kl$Z@GH&2u)E_xwmMAD=bUB;^@(#kk9qc!O5_j zb>kM6XCQ!A0Iylh;<^puJK-3;lCF|#6dXW~y2_wxx!M^|7W=EZTEblfW*baM07Z_m ztOPH!hV?{ogopUVNuVX1Py(MOftD?$KPM6}0ci+8DKN^D?ivHS^SOC82_=`3CD$}; zgv!lBBjEdHah>xW{1Xt(Z7DT6_@HX?jkq3Sjp`! ziU%^dt2R3tS~fhKa`AK0>!JS&YXjE#ITTu+HC3jxdy%nbUL~t%X{$y|HC2&h$f_7{ zR6k!P`jdOa_EoDa)DKv7L&s$Dp1uiPKkOsc)yZWs8}(T z8-GM2l!=~6`4M7YyPC|DX~*+u$FC$0dKaYLWZH-Kp-HL9bkJ?+!bO%76}-gl-{>r6 zIHhMTsMWDVDP);Y+)VG_n;yqETf<{Wg;_-4o5np*u9Vr$6NmdpovFi5_H(@;RZShWIfkE0y?Zilt2fR%wbi#()vJrp z)1FGbVm`A^T9?k)52wY;JJ0ciH4@fGyc!=6Dq}e}%yf@^L8s3c1N*bD)t%Ao*Az>^Tv% zox65HxIa)wn&RQ+l65Gp)@j?bG}f`Gww&y*h;a&c4f!w!Q4he!?%L53{$B6Ha}jZ# z%QoWOEgWTP(4?7Wqt|gTLA(_?_1;b7`sa9ocu8o0f`Y#*_C#v`uDGn0&)W6n9)7}4 z4rL=oO)Pzmrixa3hN4_r2X>BTmDbo#_WK7nA%a(_d@Bc%ufsC_6HM*lRzw|~7Kc7p zE!4G;T#dA44h8p8Vh^{bV=(FizYt961GB@s@B)PI17#{mBV~6Jsa%7U2jlbXx$(zs zK9ouA56@aZv&QmM6t$I%Wp<4(GM;aOuwH1=SQg_DNgd|0xrh7b3~l0WpPdCymtRkf zw7W@$nG*yVTm58)wqzv6-)_&u>Zy7dVdezGeHMTAj$P^U9(N?-&$jqKq`TnGRVMs_ zPNos&gK-#tKr28&;&`h)Yu8$qkz)P0lKT4(k~e*?ioRFtt7jh!z~h=2VE`{VL~9B~ zU=nBXHBS#xV~i1}**GV^_WV9e$-osfaNRaxHk4w7)g;s|^qj<3hdD3i|8o9s=KtgT zf02J3?f4D3`xa{7|Ce_nh}Z+Z{xevRA*seCQEjUY5k%E(|8F z8ezC|KFh66VjdOd;AE`FggGRM`OH>y-Y*OW+8UG}7iNzn=2l@2P15`!s(Nwx z+~%O{rfpQY`@kL`@c1Q?8x)m&R7G&di{8>wPvw==o}s0^X%Fl&FTGLiCvweCZ&cff zTz%<{YB!N#c7k5ckSBWnl`hFv9plgs1_1C+tM4= zJYr{idZXG#?CeNyRKtj!o#~Bg6|r-6dZXGz?3|O{s0#0$%lZeRJTB{B5e#PxdQzq2}3#%mMbaVK#%n!ZwEixR(_wV`j@F9=jX~l-jg+Rcs7^nrGeuith-v@-nM7jmOaD0 z+{0RW4l51ihBS`rZ}H;P+`H2(p#-l;rRR|^@wo-}QySlT*YbLT@18sO&gFj*|AYLG z^1n0xd+@&x|1abJDE_rydn*5D@qaG=HU4d)Tz;B-whfVVPFK12F3~BJ zSK2q|Y0F_lC+HapvgIEm=Af!-?94Rq?wGiBH>Mc^C3;_Q-lmI+(Ds54BOhm2|D&2N zh0V!`+goqJFi-eS0uIkgx8?fxOf-fyl_$Y7o3=<<_j2NRN6tHPfjZb*xdu9eX?HaruTMCFvJ;&k zQM*-KQ51C;e1hh2d;@;en~M3*~>*NtgG!hxdz{=-*U+E4Y}q-vJwM(}cVX zYvFK&C2sq@ieFC&WA?zFUCQ9UR0bN1{vZHDN}2GFg02oSuOSmb?M_KGUAx;0_a1#R z;rp0Y*X{xyT0cA07v6?*WcKV-$Wfu}z*nC5&8O{zEYy0!yGe}`=9P2!Li`>%y}VgC zrq-*?&7PI1+yPz__IqRhmQ;6|#}Va!fp{?5T-y}&u~PY%dz;jom0O5+eh@yuho;(A zz7&6t%ti6<)lxvElyj26ys>RP&^xxE{0O1F%g+MNq>v+$K5<3Xb(m859|X<6K&XyZ zg<5@I>g?93p(Qp{3#zO3aWj& zMQi&1`y|_)%fCYtmhKi=S}kEqw>^>1t7F5e`SG7d;W%;BLAY$OqbXbmNnxCoaEU~g zC4!vZ9Scy&ug4Iz=UXeQ_1&%cic_Yz?c6zhu=m1ZYqr?=p3~QyWqd~ArECeAgl2gz zAf_m(o-P~xh2reuoXWUH)Uzdk$bM^n{PtpDvN!IBO4bkUqJHQ#gc{r+A6Et}br&bT zLv?~s(N)AySLOW_GUog-=wIzoxXW=z40cuLg7NHO{7%R#of?@8dH(CyDte823K;b~|->K~;? zf96)Hqxshi;f?%%fd6~={{sI%eW%dXt$mupy9kNVJTh8<%L z3Khcp6^|OGdG5FA-(NFR{rwz#wB_cm;LLKPy5vYJS5NCPOUqVmjsBV?tP(=)u0=rq zi2Dg$c}MHqu}+U^b$8Jb4RZxd{?ccy!uAjc97FA?=n`Uo+uD89VBSDoJ&>>9hk@V9 zcVdx+?QaXU53YAj`&aDT%kPf6U5B@vyWOF1f23{V+GV%7gsIlFyQ==WRnMUB`m+3ml5fFpNnfzgTASpb{yhiAj*t_y@c z#bY(E_tRRV*vr5cGdA`22x*fh{^GG(IC$*6 z;`&*S)iT3lrAeA#;Ux-Mba?ESgnhtcwbby~FAIB}$7;c1*wJ$*HhGlR8b06{h>6#E ztX3W#%bbn;d#u(c9=ofs(mnkvz0eG8|9_>snW3kcp%+IVK9m19i@PpDq|?k1fW^440n!wN~$%fZpivYuh?C8*uEWP9C5 zXsB(u!O_{vJTBYY;@+x$B%+|Mf;+n7nS9@lrE<$hH9wDt0Q&c0&05iiq`SNKE46iy4v)0z>cANDNeJ!YP}PHE_IIbUR6w+VE2x3 zuc7XmH#S&a`heE`byOp-8W9wHIX z`&qmQdo975u*$PLw}CbHyXCIIMR)7)g09wZQXsWb3@}O5fMHr`ye`CR^!sevv~DT2 zq~oIktYH<_&85?Ie`guBZqZ3=3)#hEi!|rw;*5Oz*O-zzp5$l8e_?jE+H$rZS=j*P zcEfbER$fDr++C?l#H~*tH2ZXC^CP?X7DU;MRs~yOvOWFnl{Y}Va*?u?E9prYi*=po zTr6b!@Cj@$zENNYbNGBdk&~-gV&sb*wrmbRO_ZzsTWK-QB%=-PM&`Z5F^kv1Ch(_- z0`bL2g)Fz9b&mQjMRnOV)>7EmbevT31xra5Z9e8Yv z)2+`G*#S)irQ%sB6{iX6z5H*2JC0LOOzYP4tx433oEaQ5Ex3=s*0_{=rWU7t`#reN zr^4UHd#4Z0I(@sK9WT=%Xoh{R+Cj$V{a%qDK1kjR>~WU)MjkEhOdNLB>?MlKU#53O zf7+b}dNi2J)L3mb?Jl7-+-8tWwm1Uz5IqkEE-l#BVT>Ldn5pyNVSwjB(Rb;5YY&$5 zG-K(=E;uE-ruF5qtvtB8l9!K3F zsH*j}x|3kiZRDptKf1cSo+j;aQ@0+X=wBo!ntzQ@!9{?o^Qb6D?aHiAx%VOS?#-ke z*+D#48|~BCK`f>&yCGGO9Hqrse4&P zjuG-U#PBvj_=q^mnT95BdLSRvv@ZtwDudm4HT^`b$|fq&pMt9T-I;Ym=<}uoFnwP6 zqo{|Z%G#{h>Wk_Q)eh;;p2WvAaRz|+T8uEDam`;O6yfX9_Z#{iixTMbP+~+79!;8| zy*y8oVEIcsN08C&gQ`4x$Cj0UW}UiRHt6~Qet^`*i{EYX*xnU=?C>e5wFF=##NLzI zMdb1=ZBu#gk7=y%{x(qfh<;$V9Ub`pF42{b@9Epv`V^X67`9x#$SBDX(UG^;L7E91 z(?X&y(-%e8_kHB2^=YX+qYvZZ5BTaG6uHxmGo%a)!d3nh$^M5504qPzS2D|)9{%-e zPo8w9rZ7R}twgRVSf;<^-iAX*YghT#frZYy3|jDfesD|f?f6zU^273JfspR^Bv<(v zKkgl%>lc$AN#x#A$yf?9lk=@e#!Qs=8}tVEMi$fj%aP=@$`JQY15^~xrQ zhiC|f!}a`B{-R7;AF&^CtJX(|u6q~Z3cOO23NHm}<6J)2TtZ6jn?%L?GQ3CiN(~k^ z+v?*bXUN##2zY*ZWWx8SF~gxFU6W%vg=W(Jg37z0QhSypg35aYqIz%4W_~P3RjAF6 z&{5mN;FUZ35EX}pC@%BSUYGZ|Z;B7=v&iQgQ+%}R<>Nd%#mCm3Z&fXSVv5h>^*B#W z@wv6`^VKOn+9LDmJQMp2-S7EMJI**@q@sKUl{xLVam_|ejEny zWm$VAS12;CPNZEsgW0OP36OSwgYV6Cd4#NNmB8;C2}mP+KBp<)PwdG)>WH1fnGdif zyPImsm3RzpkWY9v#dXdz^ynyGvz3S8e<0DI_xNrI9cD*>3acY{|oJ4c0x3}dhe0vjpwP}vZ+u6Z- z3S5We0GCqGn>$J(?^F0TDcHpVOXGW_flcd18uSysO?RfYTj|qi?DWJtK2yI{9)Iv7 zmjQ?;F9hFKeG7d83lYk`T3a3i-}`X3<8|eaTq%TqUy)TzvvKJ=M_2QEDJm)0VmaKY(&F8{XV7$OvCO>$gcuW4pcptL#Ue=u6$v!<{N$(ZYr8hO6 zr8iSN@>znGP00)Z(I*7o1<`k5^j#Ev7f0WL=({BPE{(q1MBi;7f1(*-1Bs=5XQs2I)S0cUQa7DM zY%2tucSkAp=({gg(fJy+UfD@+!pz$W)3+67v0)BO(CMGEHELjMl$mL#hBX)%*g#FP zfK6E08kh!)#$p<5W?C$#oGk2D(t4UgIUe)^OyP5KO|l$n!ax6K-d6DVW?q=@%s>gB zX(|0?21)}nQo?6iNOpGf<+?W{?v;(^7hV%k&aw zemN7RU-$1a4>GZ32f3yj|e1fp-WTG4M`-qXyn3aI67t zZyZ64l3ZtAyVmFuoS`R*s5bdg@ z&A%G{s<-n0>@%<})-4F0O&^8fq{{;Kx!|H2IZY76pzVg`RT5c#7Yo0fmI8u_CN zp4MMYN&e{iruA3*dLe%`I{BlnoHo9ipZw8XPV28WDSz~D)B3BS${+pRwEk+n@<*LF zt-qSI{85`t>#ue$e^hYO`m6EF|C=-Tt4EOkw`TBHHzEIT&)~1VL;l~H!CxJU{GV#_ zAE?(~Rd8>`T2NITiGrihQ+BkLm@%w+8ihp<_)o*CD^gfgf&VnD`YMG*U-(bMsxwnq zl!*T{ta>|zMWOb;8dhDW=%OWz>6Vc1O{|B~c09MLu3RC8=u?sE!6;tNr@I>*O)o1A zh0p0dO22MXyqg>G)Y&WEu#eXWvu7iWMgfHx@nQD%3na7G@L)jaby-kVUswJnec0?- zvjg`nk_*B~(ho}9li8QEw)H?inkG_T;?Usl)OG%}q?3f^Ys(@w8(tcOJHmpD#Ht6+x(=R(bib#U4#E|Yhx+rZnl3 zC%zp#_i+@Fwcn>frTRgB*f!Av)4D~~>VAQ^iY@lUG#|Y&%}0++^Et(TUco24XbFrY zX>eI>XHp>HUHI%;)h>%^kC0*g9<)Yww-SF|lAGiuxe0+et{|@xkO$aRKx@DaW#bcF zS%c4l=d-P=jXReIZv&%kp3yeXXq#uW%`@6oot9C~MtVlG;cle0>v^70o}C-7Gb-8o zeY*D4TQB!|iXe4YYSWAST{-Ez-bR>@nA}w*-R{sT44*>w_W+XIU&L2(U(J`9fO_V< zL+8!;%_vj%dEvjWF8mFYE^@v?0=}yuU>^R}F8~HM|OVSrEQSsPN79`xg6stA2fjKb9uIA~xZ| zbROQ}HT)3yZ5H9}_IrtbqmXA2FZ`&X-=SakO@lWY{4IWm^cL-RI4yOX$yGdVTF2&N zsUx@GI7v+y9aqgx+)5~I`d-P|Qfu&B1^gyex0_uLEW;HuD;Bmc*nXaF5$4X^#!H}Q z&*N~PdF(p&40EHUpgjCqPDis@h|>H*l7IRThz1Zk0ta=_*SzxUJhB_hbR`R$EYC{s%!XE&ZnGpM8K-KPmGfS zFQ$ltdBs+HB^Uy)7lCUbkVMvmyhBLNnDXY3oF6i0F1*iwLBC1p58wpW>-0JoY%kPv z=5_YO*O5)JtvK6Z->ASj+Bp);OPd8Vaf|AY7xNr1=3-Es&*Gf$Cb8RB?DF}Y6W%O0_*vSIsNVtDdORp6g0PRJp)rOxtM%O}d;LU$Yv%U^g&qdU7sg}ID_5(ewuwsWBOhMrM{pnc0@iY+KwmyZRtsLRep6{N|!r;rsF3>tf5M zb*o?c*XN%f+Uo+^6>MKa`%*m{!C{AAlokH5(x1ZqMrs~<)Fl1_+(+U6=Aqd`nSK(_#(rq6B`K?*;b9}M&8S}^9p=!sg2MN18w^(EYx@O?>7se3N0chlimM^d8gj?;G6 zQTJfIm}chw9xoliY3pw2aWZ))GvCiV$b-!G@~wRS65WK=HSDam0i7;no-_a z`&VBdwg2AX1L){`edDJu9@&Wwv_`Wf`FYL(j4Ta+tg*LX+nx01`Sc^$80aOL-ePZd z{2tP;6FtN~QMN$+s1qN`527#G%`*>i#RKeOK`xw+YQzZzb#JBFq zr$$SxCq^}t=s6x{@-5+esMfr6=uhtbi0oy^UdCrH@3A*N(~}?JNWFC$xe;zy8AM)Z zaUd>89iQ6fLZ6j@TdOBu?$2pqks$S&Ydoy{y_EdjY5B7(%zs^!1*TV);Sb`*^{G>QElN0&zUo{^N^K1I+(*b91t?bAz7HMuO_j2*Pi*n^ibjpD!no3Zk7 z&jRw)mqIRY4ta<|a&&``@^NhJ6H>ad$*?)(p$f^~JR#-d*u5vDv|y9lHHSP*A=$Sk zq8&S6n?oL@kiWH%@^QbjkkY$Njx~ooS|NXLA?4%#%R)*6HMxCr$YT`p z4;E5B?vEByx~Rz=nnSKp$Uj*~`M5t@NNGhUcWe&1Rw4h}LdwVe#X{O3d=tTY-6>5Y#?qn#HkI$jfP2ZOhz}cLeFJfuA?SJ`s#;$CD;alxL-IT(w{VE!r6m@yUeH+ zLi#*Ut+1uUOL=u_Y7bS^ss+a`gN}kP>1q$@cR30kQB##GcIf0n!Chh0+Cz3zJhk>M zr9%_c)C*MEVktOwNeaw?mSC-l9gRXa&rF!yR9}m;1s5E%KdMJ(h;q$S>)cX0B0)_p zu!@4M;Mgi*oeQ-&A$qk!H=VdqyH?bSQZW?VjYe%w$Zm(HHfKu-`}g&ns^6(Xvf!w8 z+!TdcSIE{yp}xRXODT4%dn`C~74X&-Qm;IsJuTxY1RfWNVV=tl5U4mLEM3{x{czyb?sP(Et zF1Y)RS}9~-z*8%2DKU|$Q`<^z$Hj8f%9h=Ng0?cUqn)vfjfw2YF1-AxM4}KYmHK?% z|6xt(QPK1#fzTWX(kAh%Xhl;bzEzt%E^d>|xT@?rn94+aL@rk5#)l%tI5r;&-3xdv zNXto!6!DQj7#~q<79Ueu^Hz2IxVVn9O0FXNf~#O|0X`xZ7FUfAMT~L4h3-YXUc)Nb z2aEVfAdHWwb%>9q*1@zWHS1%V7yUUH*(TkI6L|0R$od%Glpgz341Yg8R>(wQhB9Mo zV)%8Lv72J}vzf6!#Bfi`*xoHs_)}WO-W9`lwTwLz!&_R$MzT?ugR*1i$MBWeR|mNW zzclyix5ed4&Y$6Btc*3d;%8Sjb{EAB|Ytw#RPYsDy zE>c-*7|5VDdTb>U8{*&lFlmuuKwW%;O8wB7~B=7w$sE#d0V zo&uAh(FN|MFppbnYz_CuVf{6CgRm=EZ;fK4iP~{7HP&*~7mEb%_^I|qF5ZW;+NX#y z?j=gSJ?I}{W#ofJwJ(9N+86Pm+SkNKY`62&)a-NOIr(5}-6lVe4N|{PjSa;1<@TKQ z>RTE*b1v_vF1B*lKJVm;W|1Bzk?sR`gB#@I_63YOz#B}YUo?>_YIQ&5b$@>Rq~QRd zc*s)NT=hT&*lhAlg)hYsPa3daR+p#t09SDpfC^{Zfl$p%?$pfH@#5+r2n=qJk9!$l z6x_y*Cy3MQzG1A@iBu0(@`v!#KD;e6kwbwP1kJx5tF?NVLOo?n2I1j2M!$}*nT}wn zrisZaS_v=ByvG_1QtGk=MohB)?AWxF|0FH_uw}iMafE=r_%J zRplSjM+Hi3z-bDj-le-8vlaeAb9=GlN-5b^X>ALwQ5cyMwgG#onxb+t#!r1e1EuouZ;X><9dZ$%lTRLA$hl<=qR9`nClJ)H$1D?^^zM@HS_f8 zay=p@RrG0iqm^jEYqk$D$A5}^D!6O;NNwUR^2?)M`6aI2weDAZ&^#wF&w;_c(5-@? z`?XxHxOSH7OAzc{WbT!9_u^u%tAESfOE8et)nDwZEuoPBEUYc{z@pkV9zX@Xwze%W zYU1OlSrHedJTTMjuD6_f z?Y^M;ft_d3u1AAjVzr_D(Uqj0Am;)S*~mqc1tREg8^v!vXHxeO@1y8Z(&u8YQMT$A zX7wf~S|mEASGA%S&6@JabkJN~B-AWaDn0!u?3v7SyKkJ0^4RwTXtd`?=JMto_fEH` zyK$Ex=h*u=yt!;R2k+LBdw0V}>Z}W!D>w$3%MUc8?4<%3^-NXHf-~}+v3nKwi+{a1 zz8CTv&6i5vyNgQ&rOJAkFfDs{MPvWt7C6tkG=gdCFXlXZ{;b0;$m2)IF)fyU)0}>3 zNF-l?7=@ty-XgDP;GNY8|b5O6HgP`G1!G zk?6ZB`tBQjkBGhsGxh)E1dgj>b$>m#5{G>=@Ou*O7=K*?r{~mT{HK6rh$eRH`>iSN zq~A>NgzOFk>2v;zU)vSuB>OpgaOKBsR@=pa@-;9`z8n`3%=GVa{6r3QjB-3LhqlS& z_@NwMiyYgDz}F*3RX$QfD;Vu1FnzHcf59jGVC1t_K2JuDgYfCGU3Z0Jb0i3_j=ZFN zG_|9ACgqTdQ;z?@;l3e<)w0(#*AP|w8=6t}?s3-cB7RJDLUr?-Q-ko(j@6QOCOg(} z)7EgqGpR9?uAK^x)w+N5GdEotl%FSdukE`JmQd-unlKyb0%u)s+wi3}nXscSXH!S= z^=Z<1ykHXNPf6m4^Z!{AqJL_gK6$#GD2XxdpCowRlmx%`PZNyt)Rg9m>c5lbIaAVn z1+o7nIX0#F;J=mT>!zgn-2YRWFTUX4$>-~*q**2QzmU(g*|Yq(^~Y(wIKiWU+G#6r zD^TCI>_|EXx+}=$!p~cOEM*C&a*k)v_jN9pdV+Fd9QO76>8X7CjfC{rTy{VbT%~>0 zZ9X5X`;uL=((XWZ1N;VZ+8qS!_m8XFkapS~EJO=uc!LOW=v2hvF(N##F^0U)M!y!u z5T^XgL@Db|AQwT2*O+WaQGk|l{;XZbYH6g*?-$3|y z9}q%O!cR4F`U%43U8~QjahP={Sx&d9=TuOXQ|0sbwG3p;re`HS}EI> zVc)Cw9-dr{<_i6ZYz6sVPz8|!_H~WbjuuZ^@cyj zE6-U>vg7=0hovy(hB;?nP*t3~lWMOcXvJ2BsNT@X=GT4yGF3LerWiF<_Oj_j^5Kn* zNM1IX)JoFd%=*Y=!*HG)r!+2-i+kf-gkQBRNaMpS^Au%)wwFom35|y(U&rftmgWv* zaSkcM{}L(|dNxkYdr^dMF(Ojp=3Ct>AQF|koRnu(vUZaVQo4dyDm0q^F)a#)2sQ4Pm^ZJ8y$*MB~3X^3k^~S{Eo)<24^%1`E z`)-<(hv7@%B|L}z)wk<+84ub;p|DQ%JY>yU;bZW?+YAIpFOhbmr{}-~nDa+q5qHFcd~df^z$Xsk7%rvz|0BcBfsDz}>AZ z2d7luD7B8oJhkGEBha~fGwrke^f69>uEPGSH*wVxUZ`_J7?B&9Yf4ZK$z;P9sRPp! zPtv0-P!VFo=^~Jn{angh8&9YpdB!E@O1Qi7rPgGASpHgu*0Y?`B5*(D$)HJ~_36{2 zccrps8~QVG*)5LCPC#MuwNuLOon${A#HVSolwDRf>h++odgrX{-bY?yqb@?YTPEr_xdSEsC#Gwa?a`QAo> z`r5S7mNFhS>A0!Q`4P9|Cgb%-$y49epEd{ zc7DTP_k1!m%D+}pC-Tp=>lD-a)Rp{e#UlmFQ~duY|BFa4{!ar>%;P6W&kJvdSO4}w zIG>_sN;E&h-L9h*eID0mxL$i{X0Vj`(4+W$?J5EOn-h&(1D96%D`MG+3Z zW`zqqZgGTrzi^8@uD>-xYRVsSbX1WsW$0PGRmlDb*%l!`DrBA1|}|Xj@)raNRm;WOS(ImVo8^k4ek)7O#Tm z&%twAW9^i~kQ9ckLor>>RC!i^4~Df^paocrDwFEs@E)khbsnz%Idvx+^x6J_y7P57 z`@*}GqS{2pEXLN;oN|s)Y3j^koMvw$&2KaLoM&{D=67)Q)?H8GYD!}{`D)BN{Mj~H zP1C$Xs@xW1FB-~zsq)LF))MFQQTZ{BYT0gVOrATX(>Ur4jY>03S<->Eu&%htihz2d z{^|63|I}iAW_$B42^ox#Wh6(Z_))2XpY=0Uh0jA-n%@ozi0)Wimh068~=BfKB) zNc7J|{p330eo=g!JC^|t{a-5zzXu4n3}s(0D3pDzoS*twgqjbE*-9%9T0F)m_PZ$1g@YIsh{>qo*a{a;9 zJaG`g%Jm!da!tFdnA60Cc{OmfO7aWBYlW}{;iHYheb5Sb%onba%)0d|-2P?t@)H!7 zUoQ0#`1(rB*YjKRH4*WpTa&79fet5R(k160tBLeu^rmTT57$}!9B|Wu?hAE9+Obg@ z74c^pY=7F=-qEvd^s)VDY%i~~EvUYZP21GttHw9_j_odPY9~E~;d#;UMH3I7q!t(A zL53%!d10O^=7%jWLTmHs)B(t1b1aJknrYJZY(Ojo>1_CQD7NV_Y7JXZPP?>bUk$Q3 zE19@W?*v_JkuZl}N%)cB%t)BoG_q~_(2;EV&`s;Oc!T{!?h~yEUx`_kOeuY+Tww;Ma&} zYqg7;^EWj4nkf^6j}joBiA*!7#1n)dlMAn4m~W3I+?B@4p8L84a;3y$(}TRPs++9L zwN1`s;x&};kROB7Dai^&JzIVA@NK&Ns-AH58=FeFRF8+mQr{#`eh-Llorh$3jYb0! zstD}}Ro^!|m0@>fS*tsl-6Na6^kohm&2UuXFg@xv*3MvbnDzg?J9L=H{2u7|gDp8; zjXgZqJC=qlX-fWS+Z+0k(llNh58sM&*H4}cEbjNDj`$-2U9))aPCe<{xW}H@&5p2d zZ(uoGkP?y1##ao`6_g&O_?GlIemwx4nUi!s$ zqt-_54Nh8$&Os;YloX#*D^++GVd-n$@B~K0fddx z>Cw8mW&u1_y`nSh8xmM)GClS}!!8zu@O$Lb{nF$m1lMYxt2^AU0eLlS&74^oez+m1 zAnq56vM&YEKD&`;DFIRs<3hD$$a_xro(RMtnG`)xRGhN-oT;9W^TlX-R z_pri{$FBGIBn=_IiR(PV3fKp>loXf^=x`6RFfvp63a~m3FdZKXuv;8pI(`&juQ))R zDY83Pd3+is+>a2L*@BY%u?ZEbH4W)CTAky6E$lyQby}r{)T{zoBZG=n6;&X;a@t&P z+J?t?M-vIOr&$@O!erO_H&MN-kWYImd8Q_5*E9W@rh9_Q z0T3OW+=W8|X2Z~*$Ukuz+sg;aXM=p)O9B0Ol&Ray3aT7V%D98@qD}vt%nx}~!h`b; zFT;O2{J|o*ulGqG>JGuFUed3r_CZei3v}Em;|^6CxuEidx(AO-_>jn9mWaYO;}4G# zkq?O+5hZeBpJ@a(NPDr^gQl^-n$0nwWXfwz^gU8vS+_BOXLPPHqitpzg|9cC2x4~7kY zN8&Z^7t}*!72-st!nJ}rHYe;!!g9kvSV_lZ^JI-n1t#Nk>Nfc1_zQC)FI5w2-QeF8 zflk&1s%2w8vh&5f*1vuVPCc$UnuO~gs@0puy5!eCu;aq=58j+QJCc9-&%pM?lsAjO zqqdvY^@V#x!F}uWrLbBy0~q`1SK<53#`o_LN2Sah2J6igK3qo9#}jDhMf#FE2e-{D z^5ehDx^r<2-DGwVeSuV45S8j-d8hqSXO~}9h8?!<3-)O9(6Qmk2r)esP5P>%7xI0J z@!_lUKnfFU;RhMy!mIhI9f#0^s(wpmxw8;t2U3|-%EGxD2-5%elWOcPs*WmH<+n22 z!$w^u*v!!iZ$C8rn=%=hhg4u|YGxzS{U_m7Hx+rP&umf-6?%uTE+I_#UK?*441_m< zc)22oj?JS!L>}I%JQ%8wEsf`45))`U_LUFC#HGO-O+jaGp!|V`dw#a>gps!Ljd)kQ z+gNI2QK`*H+f2HaaG`9LrqB74juf=XJjI0r{Io9RXB|*@8QvJ;7JqpY5C`Hi!$mHC zwK{S7wX5oHlVg$n7dcrQmT-8pPWe7Bc1b~MsI>bJJlx;piwqRjt540B3q*LN-CyOc zIoRLTlI=yZe+Z&XbEz|s$J>xci@SvCjIN@^y-oG{FU*=tS1t=$GT|&(E3}y2p!{28 zfR*L*RQH(g4&_?n={j>*e`>+K7S9gO4mVQAubl@Ul*0Iz{klMNg}0M#DV)7m&T$ zN`@e_4Xp>lvJ-D5qm`3MteaEO+1e_dZ_Bk}GNCpBn3L@UovtfTQ2qOi&E*D)@JXA| z!&u$ll6|=bxs9w$yLk&D3T0MyBUrWvEGoou5UczI@ii<=$2Wk<%j&ae;M$CMjsoIv zXP6-*&-x&~kwrxgds;;?+E%j~)^Lk0)a90xva}HyrenL;^z3cv&EE;)mb6sLvrMjc zZJPVYso7`-dHo4h)Y;v>Z*W_mVt;x++ zPLmrIdy^Yga+4bsZj&2TYm*ywXOkPXW0M>8Op}|Xf0O&$Q{44B7nkuA|L-=rqcY_V zQL;?AirbXNKPzLNYikNWwao9G;y<+xaMZFX{{MEJ;CM+BeU5rGx&NnihArnNdjG5r zu{GHg{{Qbfh03-meN>Z8?x}SSRa=ukYO*Hx)H;cpsmcHAQ{3N};zotj6#iRN-2byW zKDhaNQ|NJQxhcJ;r?{V);{L%D_YbGIQ3o~AL+z1pYu0ljU9~MkVj#SkqwexKNj}mm z_0t01Ve_F=Kn;%0UB=u@zLh-P{$Qadv`rrjQruT4E>jbI)9wN$jG9#RgtLh-bVTTE;2`XhN7!w@bA_{c=l1aUH?4mw zHBIrAu1`HEKYl4wwt-aYmQ#|*`_G=8<0D;19odyEz6X zM9gEXkIkB0t`5x+95$5;1sS6b*|xUX+u7`0q0+~Mf!mfjl@0v6^atguE^BP{wzA7x z%tYr=mypfYOp7U;*q9{_TIE)+JD3w-E={O4bJCc@_Ij$Ih2%_8m9{xxc6=jzNl&*k za%>e}ee>s6Zep&-RQz=JYj!2s^O{dO&&*$zbyvb&I-G}dOJ8<`69;x54H9OLOh>L; z7o=v-!^u=8H8AA$6AN~jl`6&MZqG`y4@cYh0MwpI^K>`_q|-nQsXPj8#aCNc0S3Ob z24}BWrLT`YK2u}6iq-fWI6?a6TNR4OMgIXA>1nd5Yl;6(YfDzOr75jkB&k#0s!r}= z9KPndHvww$&&mEv1h%kTG=C6p1~K|Gf4k_qxAh)<}aeI(0m2&y81Ybe8 zP`}2UX>2)o#gUD00oYdeWmcGhvAlN3>vM)ui}!EXMLQ7QJK;U!>j_(xyRv1c+R+{< ziSoX{`_c7lhyEHd94>-^T1N6SDMSr_P<6j$D)wn% zvtU)mDia;^c;0kh>Q9`RlK2VDncdL^3q7f|f@? z=iC`88OkU-HT=n}=h#sV@kfWAh9k=SI?`$2O}c&=vT)TZxKgSf}0 zjBs@B5>oUPgaz*BRK~K|$idUpP@U$cbx`F3+=Amecy+J0yQQ}~+uNP%)$?jfQ%@te zO`Jd+tN^uL+1(!9JX?@oFy-PI7VhYEbn&d%zG@i}0!-m(SHFG(U(8~DJ}XeBe)cNL98`B+H{PNgUZWf? z+%i{_Yp$pL#3yk@A_-Mtu9PisZzTz+{fr=^AEx)4zoo>KHo99`jIAEEu3pHpncT~` zQCOj3?)pKs9dEYa!y?90dzCB6pZmEOTNG5W9+2UB?;@*zQPl3mm|T-xB$ZV^WwkhA z6^)!QS6I`X?Osw`5|RFhaZwc_?y;ufoE+k0-eiMcfkOBfV03rpg55N@}{XqlNJw`v33IgFew zkGQ=<+=g4`YLburvgbUvKTL3|?x47=xZJ&6+$ug+)r{Ndwe(^E9mN%YHxo;deV%(~ z9oHGyy>;~kMda@HPt|4VJn}|J#dff>c1%xaoP6__zwAWY32*JMPO&#Ryi-svT#-6^ ze$%1QmmoLYwQ8IR9mE8ndZy@l44sJ1gy=}1PfIOEX}tqp>S;|e;i$Bp-zqH~K>hd9 zQu9z+o04fwF`cNie*gZd`6_Un>EBCB?L=w4Gnv*@D}aFsWlvI?bw-Bn4 zrw?#!dxkt&+>(PQpQncjns$%!8-9wMg|{{MzQ_3fo9El&WgR0khmRJ-j2jejuD+th z=3ApN@4wiZ_hju?4`PD-EYrpCiiBW455fyJ?Vgb5gW~ON4c@LZ-u~fvGny=Dj5pp0 zjEqbOO7NCRosC?_V&2-X*-Ea-mRINP53}RsuV^HHizTmnwG^pE%R1&8h^AOiUXV{- z`#YGTOl=>hJ+XD#$=Y2{`@iYP{Gm&B8U&jdzTDvBQR9QdNaDjt@QR!9FKPtTBfMwCOL?VL+Sy;s&&dE)!oso9N;O>`^q_ z)6IE2* zHyG8ck(#Jl>i;GuXW4U8#U7Jkn|7grp1ya6d8uL`E3$#^iOg zyd*8|GOfA7bYGdtsJ&=MZrv;YqK@Mlf7EH?QnN1fwU&@AuW0_qCNhsH&4>$ z_F-}^TjIsc+TOJ?G(B>J%=7NwTHZjf8U9!!_RFE)J-cgmd81Gv$r?VA-s`o2z8i`Y zw-bRYD>XNf%!-kH-eSl2J*}8%Fi^dJF<2p2T z?5*B}h{pX@;8#-ay@j;jyv|?A8O?s*DYv>I$c(aBQ5W5+K@o4sY37EFq`BHH!y+vSEb}y1J`;q)qOnJkMln8(P~dtESvMMV zS*Ekmt|vO1Q4z62DA|=y2jPf_?3E3m!=+Fg)MWR z5}|@a)dWXmjKvpQE%IDFDhhBBREH^^4kpCQQ_?J(?s#P9bt^bw5HZsKcAMB9ciKXiP933Osi3r zq{XZ8^|H*l6VYf@Pe*Mv+EeG@KzQg4PbFh96JJWiapT5BGY?ku@bH#84}y|BG^#>^ z2S4?!h=<8$9u6@cPMd}Y#c?a9;lXMRJfI$m#}@>H18`{DAQ6OX5M&Upwclgy z_c;4KUcX*ow-a>a_!1^y_x2ci4^q>J$p91yfwi2h2;K?4a)G+$7g1_-?%$|)?(t|9 zQN4?h<97z(VV01lIqvQ#p>`!C4BAO*AvR)*?e&CMEy+&oy-H|<5_+*uD0N-DG*Ms; z#EHd|5<1)xvLd)IN~lu_34`YPqe=) z()U2RjulP5?_dHqtH9B_zNoI|-NP_$wXk<>?^RLDwPtVi?K?quG@NN$-{!YA;~Hbe zRav6zGFq*-F%>v9+j{Bf@fsU5T`m3G6IcRw<&Pn(X!qC7+1b?1lpeWIFW5x9sw>j| zh=0GPkvq<|S;IJrOhyw5=24fwKxSz^pRX5?AAfVHRmg3(hTPN~+|jYztQo0~ig+>K zLu0wseVgPaY~DR0xv5bloM~)?u_lJyQIo^OAcW=m1t=+AS<)}WvI_ij& z`5qn1vF_U>M`81h5vyQ*MnaAj-g<|BO^y>!G4DZ?yMCE(U7C>~kDgJGTfL*BO1Li@ za*0eJJnPGjBd6Wju3U2=y=q3<90}d3Y;rbKYA4c$!FpXQ?HIpHBvdg!O>wPW%vvQe z9ws<+5;Gq+#kY3#2xcP#a#oL%^`1E;U%z$smeO$l1M zvT@gPzXac62TP`OE%{LI2SsUWnt)MHhN4EP+UU{Z2aXffB;x4?=U>M!O-vZ3@{No? zJB^GnoFDhe8a3QWv5f1}hKP{)UL4D~?%O0|VO!m2B;!vv%h%f|Jiq;{q5mG)DE*s-n)9~0_i&=Rxs`|bED7Wt?jaEC`P*YZX zhI&`DmDks^9%WMcrujiCwI}(noPr=>Sf?0t^nc<28aSuOIgVXyS%qFGRcfdIviFWq zI>qlD6^55%ueRV15@6YSgc*w@Q*1NMl6(CBWA4o35Jg-ADuN3FLIiOa_Z?*uk=3wo0TggS zamSVLet)O#?e3Yx$n$)jKi+)ObE{5OojO%@s_InLsZ-QLB54LAcNz{;4bYZR{Gw4O zII_r=GW%c~HQ-Ra6}ub`PB&0ua6)8)!(I=PPP|UZ?cJq)*_-NiuLtSfrFpXK`X@HA zhG*gGi~fu=mieYSv^NNC^Qon)jWj#XW&xf&7&NW2FFNzn+-Wv1EeUP0i58Q+4y_oj zX!-@6(T{~TdF*K9>ZJQ~MPT_Cc=;@5oEwQ1bH`82X@lHN<}~)b8E2X23QmaYqQ{8@ zw+P%hS=FJ$8yY32>cA6gY6H!2#a>s?TS-H(=`P%h?m}sT?m|=0Y_jUerg$p2jf9$? z-G0?oQ>wpq_Mun1LU`MPlaStOw-YV;sp1kKECigafGrBRBMuNn1dJ=-P6gZ*2dLx( z)D&>H0`7?eR1XB4qJVo9aGwI8M|VTG<&)wi5cbRZVy?v|%jlUvbNLHoO{q?5_KUQu zCF9gKNvLaR5@wWSwuOzIN=@KYYJ$3{)C6^@)LAlNQFLmY>3$s(rI;>U~gkZcJsSt3ODz zQ0gvmIh94;slL!e3lx*uW1q?q+CJBx?R2_C<9RI_H62Ck9gRtpm_*y zME(0icr>odq4u!otkQqc9J{mg`1Vtr|LJ+WrFG}=>ET`CecWahUC73Lq&+oNQM!zY zcB`cw9onlV6g{a4?YB8dHrm);6AH*i@v**Tzq4a~s|)RG#&vNgb+NPa{WFv;yz&9@ z3a6!3DBEsr7V2sp?rXzzVxgEOpQ{pp$|@{LSueG+3X+Z9)?QWxWTRM3z)0Jfnt+v6 z`=?e`-VULxJJT1~7?)M2md&!-wr;bme?WU6(#Gt>vQAVgP;nG7DUP;l9V8pQy}dXJ z$eKBCqFU}dQ#-Id?*qiUw!dzK<-K(HCQ3M*M=v$}AR81|V{*pB)8QKlz#vsC2yKRT zo8gOhhU;91Ya$i{Huw5>n*KvON^d|*FCFl&k^i9@OPy_U>Ojf{Y}7_)QcKl6g6xuP zI$R4IaP+_HWi5@EM8|$3d6b7-6s9f>Lpf_5#9mcrK6+S9Iolh5`@c<{kJNY^{&8~@ zhnMdQZOeUY9%<7+55{x_(OHlc6Xax&vr;`DN#!X9ZR0gdBJ~2=*{?rBb3_NDI>;m7PQ9ZA zvME}&cG4|a60HrCL~|yYd;Im8SUbs6#^-z#qknc%loySRramJ4l6E!Lai%|*| zUdr0a#)x)WO>~hZwS)-YNi$|#O=6s*b-QTk?V~km)ec(P_R)4otE8ehm!BoH3KpXj zY{#_zoRA1<(P(v8nxN=00v)YQn2O{OH$h3XCav1pPTOA94r!JA?AI;%f4sMf02CMaoKGH$|>XlL`I5?Td|Q3|$WS|1|BM7D$o zza_XOL_q6y(b_;sv?i@rfirFU9Xq7;?MAC%{6#{mU@=O;9IYH`qefz;G(N2F%e?S3 z%>=>t!=Fi=G&KIW{>m(hjZKHLuGT-Vj&4eN{c;RFgl|UOK##YyAZvjPdG&LNvhQ?K zc=Zc#+Q$1n8S`H#zc!Tj`3q8}SHD<(ZH(^oxcP10OB-+d+-3eRC|sLF`&@7SOXTNm zslxCyuWp|2DDcx3xLJYUjRVc|Jq5nc0zaw1@5h1Wxk`bM3>lXx@CR|Ad48zCqb%@p z1zsHon&%n?V#jO zAkaKNR^SgUkRdA_-58@a&rcNiNeg5TCmr1s2b$+*1#&Y7f*3&3dM&|rcFc1tp6Z*t zI)_&2=(ae-Jhv<4WnTS<3i)XqVxBDu8TIN{E98zi#5{K@1kSovA$KW+Clk$cHy-R9 zaoML&7aPSQc3sjO0N$Sf%mDz2(Zw(a0K_T>FbBnvERLZ=`{$K69L_Sgb`RM!K0-{d z&jIcgK+?W9!vXFSKr3f&rUTqBfcVtwcYp^3__zSG9N<9##JS#V2Y5(;iv*bC09yqR zFMD$x;9&tiA;3W1K^_rA9P14f9OO|!E;UGpgZxYoF|Iez=^#HBMBM5PbUDalf`~0NEBsCnv931|H^c7*5x-)` z-=+G4AYV2}+z@{hL<<&gAa0322_i=J2I8jpvmjz%Zy;`qzX-wsERX?*`DsCzpnwcI z$X^BF5CzCC4)QlazGaYI9pvwVeA^(qImkZ*VcH@&(@dUZXAXNwP1yKj| z1{OI;T9E4uve-c~f?RKq5eEqb`H?}EI7n8I8w|44L2`nyI|$~z93(HujRslfAO%6# z5G2mt4$>hA`+_J5I>R=N56NR@a%!Er5vu#RM6-IU0&GS${R9D?Rk3mIU zbo0Ccj%Cy}aUsa6OX6Z5PF)cfYXfybTr3RK^>B52b#*yhjBx5|xEQL{#c(kzs%zn5 z!$DmN7Z=ggm2mZXb#)uwF6YJxmY+`v_Xr77HaSeKPu{bWYw#3@FcJ=CFXlTE2ZlHNmlQjHi5!A|!N7tE z{)GbzC-@h6b+IZj7khQFD6SE&F4n}g#H))XaV_=gVntkgd3CWMu4P_btcPoFuP&C` z+_;&)PWKOO&(txt4=0%R1Uu0yj5cxG)~WPyy!DiLKM&@f5q7AfpRGJ%@B!j^v#)iI zpml}KUG3H7TQ!LFq|_p6Ky_?WuO5INqDJfZ2 zhAdhJr~`drsP4>>@)pok_h+y7?CaSiH-kxg6w_P65#m*+vYd5Mis|z&uH^7yPv`uL zlLG;gjcftGrf)ZSyBRx2*RsBRVA7YPrQj>0%m{m{@{5@Zvizd=QTK@sqYDA$gH($~ zh^U*jdhJ;Ti0J1UB~+s>QVCqy%03+1WSv=|v%oXF8|f6MpEDJ+cl1|wL~+Y5(s5&B zmcAJ4ITm%6`Gc6ugGA;%l5D#Z6fU;-CT%vSv86))7U^+SiG6zYe1KqZkutfs$wp5k z`$w;a=Uzctk_{Z#v#Kymz`y9D>7@asqdV_b-<^F)+JcBNy ziOm*kT6|LxqzF7aD@EYQ+Njg#mNHcp-NS`>Q>KyX@7C=aqRcM5(Q9qt<6!#3d zC41uTm%Bxi@k=HcI8b0sRaGpVZKSzZVQTX5IOf6BO`rAmct25g8IDtaYebANw91K}+q1A?lyulORH}>k|{IQ{t z4PQyqv!X9x78^3r;rGq12d~W(2XB0!FITV#9bPm@J9Jk1(7VUc6*vqW-YDf*A-0M7 z=meXnAawg)rPOZAoc)MI2bLF-idYVK1X}~SAp8}x!&qT3Gd(k%k%`?kFJdXi!B=Hr zZr1ADSZ-j}YsQM{!;kOG&sv=y%R6uX*x|=_71J~B=s!-DwKDxHbl5v%(apt7dGXXq z33GWz@tB(rxJr=vXtRu-xN2H9cOJIY%#>WK>VF*!>6|$tK6mMh&fK3_^ecp~gGOF& zj()9$w{M2)Y!qEoOqUnuvN;#PNXh9bdi)^=EtH8jQ0&6{lcbWvtb!3^m{Xtxf^ z-H5pUdQwNOPU+&oQD^o&{!WThbM+cXn>6yDf!p^anS$gf=d{fW<8RoG+x4LJH%6e?98^;e7A z!8l+X|C@;EQ)*fQn@27MX=GHs-Xi+Hd&w>;!HX3*fgp)KBE*Xw4c)_8Sgw&u9O zm48~h|C+H~K&L4+_39Ii3K#5!my`SPvXUcPfolc^S3B3*RoW&I?9?~ymP$Rs(c|w9 zdgUAGK;fqW`rBe#8eVFukn+#FT@@d`hn5cQ4oWco6=VK zTQqGKSAzdY$Z)6bn_zinRxw@S!R+u)0Q0Z5RP-Kf#tV?$HU*dBuNLtx(Knh!vxJ|8hll!We}?}~ zCxS&3|LaWiBJsb0`j>Rk2V2E)&>2f!pI#?mb(#x=<$mfYNv{)C)v4*r^~EN70#+>w ztS^s|xe6WMpE5bCsSa}+|8uC$RAp5385ON=J$??!+7q;_#lqK9Qg=`W{TsiMc(uP! z#Eth+@bP~%z4yy|$Pbdx2jo4c>3vY%dz;>eWdtfH(>F{Y7(H{IR zZ}Y^e!enD{I`w8~zF52%K1C!7_j^8^d*p)X61bz18NafaI;k^ToiV#Wyy zu-XYVh7#cez`JIhjh4-ndF31%MC0G1LbX2>{sv_1581@S_E3Iv_%xiN9xGu)q;wP@ zg>Ps4?b7!uFZ>57DU*V=QPnwroVC?SwN2vKh9Y?zHljJ&uyjfMK?<{RXK8#FpY4Kv zORk)|mE+-YjysY7y|#a^O%Xw9Ujc^-GeN6X_nycqSipI68xhHV_0vMpjQc1 z0&{FfPY5%!w=%0O#q#qZx~d<$iYY1Qj2#cwvqvm(Mkty*Fs5yPk#$qB9cnM2~F ze&*tyTt5mIQ3|taFb9>XudA@c8CTdq64M+c9ZUe`05DsCW=%FU+8C%+le~%%!})|+cWPmV&48i-=)&@qg3;fROZ+|)p0IW+NIJgrBw5iROZ+|)hd@N<5Fq5QmQ?Y zROZ+|)$3d;PSaGK8qSm|Oj4O+`&4gosaTM;ry5F9nFBPH1YiyTdkWBGN~>4{wVKIS zWgF}``eBv$3^&92WEu;Ux_>Y?G1rBHs?(Zdds^2!TG6%hcER~a3z$mp%U(=9GfM~sDuXUAM9`uC5EsjucMfv zo$ctOLT5e}!0QFA1*GZ?*P#|VACzu#aqB1TgM7l|#{-BMWr=r(qXX50 z*;ZgDzX_;+gdnf36xk?G>>e+|NLQhn#U?w`Y@$8eL^*PeBP2+OK=S>V$n+aR3Tr4T zW$$!Fk|?FJFBh~~cIM|o!$6wC4s;0*atT=*i7ia%BHV27R&OoSZkOTEny_w?@f9S|;~UnYBE+ZyVkYyO+cUkC za{22R$FXjz7lwC~D2OKFnA*fK#cUIUE;W8@Q(!-q;*8I@HWBAvF7N85_Z9MfzUdv4 z_vxm0g}et|m!v&T-uE`Wuf&UtP@5(OUWF&F)zxyk28R=lS1j?YIRGdotYhB@i4FY= zZ>W2qU+nZ_YNwr@gi2l8xCfb(>!_KET3mv`OHy1Z%xpe1Hfqs zz#IThPXOjfG9GU-QI;GjU|ye0*x~a3_4r&`ra!iO*~Ya}CI|x}*U-ciV&9g>*_gKV{Zg;+#!=Sb_+!Ro9z(K z)h81U@)3QFgdB4K*pL9s0pN@TV2%+loJG>;Agy`u4B+WoS>EOkz1Oc!r865~mBSx< z2ez$r{_cSp8cgO3!9waNUek+A(ejQYCv#9WHo9;w|4sr}9>i+``4s(K`i?VzorQxr zU4FeJz7X^ci$qd|`3ADR(1nb_n>9y8^R*KaL?1q= zl!*fmW}Sk;mmPh`(WbBs;CldEvN-gZ431lX9+f#VE;o5-|4sBOFIOkS;NCW!wW(I5 z4ixSO0sIKw3|oxb*jQlHd3R`pq;%RY%xW?6n&C1G5M9208O3)*4?SPZW{&Mm9P z*{b7Ok0zFL%xP<;_nWgM=m*Scn?C2s*_>`Zh|i`QbI^R}CIE8)5N^#s`bJY|i zk>L79e}Re^u3QDzH%hhQe@JjmY~}#5Xi`Ldqrb)ox(E7zg@;l-?Yt~^g;I8rcvdoj#O!`vwfxg zI>{Lr?%e^NGAJnQc39xB7!QL1GOUxoj+BFcUae0pAxga33^_j{XZQg~XowisC=&55 zJ+&p`g=17j<6{a>hcCoUm%0ES{l-sy1H6~3tj`U%tTVRbwtb327)j$@u=Byo>zFqw z7eBh0kIT`yEnA3aBgl_#AyV{PmIvz|qlgW)f*;)qkWM_4LaGz<+-)xE#}gh&!2G!X z{+bF_J?};tB*(S4mmj`VC?+>kvwjQHllt{jFDCt`EWN5$x0Gg)mD>$6h#$=c;78wJ zZjzzgISNMKM%CYf&b2rL`m6E7w|CvI0Iwf@mxQc@ge>4%ibJ0r!osTL?KmKabCKMO z8vLYO5~y+sBIUv;<;wpuzMr00en*x1{Pwg8DlE|;f9AW){b(0F9TTf7O9lx)?sG`O z*S__S&rHlO#u8WE{9rpw37({X%=gOSF>1@W4G*IXpPh))*UMt4(y7*3Jj8wv@h1xT<7I+y1>a0IIzjWLTTko`-p_B3*Kgv@2N?a2ZXhAL177Ml;(tN$weaww zKgcd0yjUny7IF~|2X9TH4-ss*2{K&t7!&aLIZSuMPAE5s)+c9e&5 z(BWlvh_1;%-}OYrqdN>)tQZz3x==9NU0 z8b$RLwKw9NhtazF&7wj*GCE!$Ah!UCUK4w6mFI-mbDKP`k3F}`b0VG+nqI}Ap{JYO z1!MODqv#A$kFn$RCFtx<4|}~swfSminU*`pPkB8&tI~LhOgHLwzlJlc0f8`F$seQ^ zZcX=0sV!B087oP)uGrCCKzOi1rHPhp7kW34+*lGmim&lfQpIWS$FB!lY+5O0coLy9 zkgY31q}c|YjgDv;)0Hh{i-|?|%*X?A`g@gLMh5m!4U@ie^h%lnjkV5*jWsh85Iw3$ zyo!*CUPJvyd+_I=cM!|@F$J)vnx!jxSn;OL!y3Q~9pDiGrp@D4|6u}5El#WT3sfZX z&*W%Q-cv4>DzmuoFL@2BOKnt_ut&gK3B{g~hvTB{r%1(!J-DW|f?{U*3sHH9i*=un z%kBZ&d{(U9%I0JtF5KuZW=8IIF=JE@D8_;Oaeii!2l140 zdS;=*UMsc`rbmBcqA&5xLZs875W|f@sjt|#&!5-=#&Q9?8kA-hXZEc)RQuN_0%Z#{ zwT$0u`F$vc3I6YXXiQ89D-icke)=LFSeV>N`oyiTeu_Jq;SV(G=xUt~KleG>05QnU zLt)cRR}&Qdfk>lE+7eu+1iMa1Akd~SG-Gdvo^w<3Ye)b3O;Y`E$nUn$HflJ=ofLh` zri+`=uOQS5UqIdOdtqBj9lTd(Bzed;%mWp@mPO^%7(V>wV%hDrs92SiF?gLNK59bZ z=Qk7YQ|vqC2voYGl{DWvD$uLmP}v>e3k7_U+H^*7Mzx0*GtOf`9?s)9SB^_#PCE!% z!96VQN}jFAnM}C+hG(9Awr5JYaTFc3XG+Xz_Yl8vr1fpVvvs+n1ted%#CfuH1w;!0 zmHN3rya-2Y^eOfyqtEEM|9tk@AX-fDaMn>X#5C?B8uQT-A)2yY*ykPI zH+IbEY4j!SN4=M-zEbl+RK=1*d2zqL#&d2SPPQlRKg>9}b}@rt`fx0ipk^f~=nd|# z8JX5}F6l`J#~@*tMMA?jayx?59@r~Yny!2+P1CugJDocwULB zAPMv40Px8KU=9G6B>-~(xI6)v1Hh*efH_R&I+!}tzj%@gS3#MZoxE_h+%`FR;cMl# zxycJ(C$~*anERJoGZSkCx5SZ*l}>#LoL{DGc)BV&Z4e0{93~R;;t(HAPd%xgDLo!j zht`h*^Cc84-7f!0fQF~0!?*J-ro#<g&W?C4p@``zX4(ejI>LQ(b$;+HR1aC{Fjyl#oTXz4{ z)cCvM*HHSU;pa#!p=<(h71bQxPKb-BMssrhF8{f1!gWxAIA-gWMSQ=ee(oEX0(7-V z?$}ypg>(whk)h`|;oJJNw#FoBYS&#;+>l94QC%K^|LfrL#d7eb>PiB>HYWP_-VK-S zO6Z~RCDdLye8n7L?kDfZ%r(u_Mf@sj5mj05P?ArOTDHHdCwgf(M&zO6W}K}VXkK~X zRTs_c8(4VJMzJ`ute=_(9p8YCOn9y+@K{CF-l{_MOi72Q;nZYh3+I3UJmw-~z zO*uYsqnpB_RCBM;*8bmQ~ngnNIvzaV$I@kIlfgx>g)+?kWL@zJ;vxAG9LSHb*E z)%AKoU9~Jl;&o+lWYVqX|Jb7FC$5u?N`9nl`k*UFjz~o#C6g#FDyr%7QC+Z z6)Rc>J&CNGAKA(?%Up$0W4buvUT!}Dh<>j<0t99|sX@P~Rshv=k3G%B7SpJ&#Q0sC z#FX}lQm&D7R@Sv;&&y>kS`f{X2Kl=DSOzl`x!Rn6 zUz(U&ta?UYLHoI=j@JhcoWQuM7_9^b4z$9SY89pI zZ9`s~J|X!bD~z4WrM}7>B>vrq9!FL$i$>MXeaQXD>c!H?LoTd1y^RM$%adhFwX8cv zYN^&DXzx~=sbqs2Db`r2>=nyv%W-AJD9eU&dkiCNCvXd|iI#<&)>2r~AMVXnxf3LH zA8LAk2KK7g%bSEfO_&Dzs^81}+e;ssN7=AK7VW8TJ{mM{w2w*{n;pw9zZ9cr#eB?W z2{&7ttT-{eni6zmYYR!Z^B7AkhSB`*n0AyXdY^B`sP6W}Rc3&4#C{$k!FG}$6 z?eC1Skt3Xo`GkugQAE(dswI14&D&j!WA&cELoA&YhS^LAD#hg1T#Rkx{-WjSwo8|< zU~ET5P}#iHL43bM*=i4hyAU^~v8TUxC#nEJbP0>uq2@UGMdR2eob?dK3kvfq*Pl8e zt;UKNpkoeqj&d}4eb?&I`kwZcIQ?fUfQ3lkiq6WSY;J7EE4gB0OA}R#El&I;elb17 z&wNXRyy9w`s3V(Ol+I0G^5fp2?>Hgdt6{5{8{sq)$0hpMYpjj03Q${z;MVvWPHWe6 zssugXgPxw0#L2hO5qeUY@GQQL-q#bZe=JVEE(GbuH}uKWzs#rcP3Kl7jc+-(vT1zV zxs^}jJ93vB-*unw=~GwS9(5^a0xSdD`~ij8KtS;u}<4g%es}{7BA;$>1&7<#{)sCPM$Z|TY-Y= zGV9x^1Almq*$Wl%*h|{-zUF&a?;vzK6RtAUF8N!?(00kke}v?mydmhwwmaEIKMFnD zy)Nf(4jr;J+(ZVOck%&DpPTfj`G~9A1H`65>MH8Rw@o+oLFXEL7UCE4qT!c*G=V8p-uO_F21_WxFik;*_Qrr#EG-4^X&=`7K~Xf0Eyk?0wBbygH8G$M}ujiA0uN z$BC1tmsy6fOm!55B`>`>g=kmDjIIR}x}yCU47>7!&r)-$xKHf3BzdRNkEv8WRig65 z>ESGd?}F7_a@jXO^5E#1MhcHXdwSbu=cIV-kY*X0!}q{STHd-r)p7}}GDepgI#1{@ zuK%CVbw*t~NB20jm0ib;6i+5tcaDyYQb}{s^Hfo;?0LIqqRM!497;7DKzOO#Qx1#7 z);W;qFI&uZHxD3M2qKDUPnW|pDRq2hRY?@k!?(`@*yFjEOC7DMJ!dpD;xU-xX-D`! z5Z@j3Lk@2XOcftLvt8>-I@MehKFZt^z7tZyckyTRT4eX^xT;~fUoPuNm=TmdBV^xcP7?@$=`;B=FHhf*vL;SagQ#?apoaOWVl`Z zE7!2?rLfiq5JK}XHX&Lp!HYDm8@WJJu!y-TQ;C_UI3u3rUGHKIsg-10+>r-bbm-bo z{IamzGQ^rIUqXlXRT^t+zwk6LiIZre^r{q1+NHh8A zmb{?ktQ&ToTxSkS`1~C)(r+30;t2G%L&QjkvSOrHPHxq>6f;eJHC=(Wsx3M&ReLBn z!gs^%&30}}F>*H%_fxwa=+I4X>&hNs5>t23d1pok5#Tznb=}JDO>%^;)zS{ky@;8$HA1WUc2QHVb6GL}=C02mT%&N3)Wbww@*vQXqN7~KE%isPt8IG1<6k+oiNyy3FbNDhZYfsA`@&{H$n+W8} z@IrYx>-;O7Jdt4}H@Uby-uP}mnb7Hp91jR(Z~P#_lDdq=kD+p*Qg#F)14ziW?iW)i zd3@wi7wqJPB+oJO)IWdtf03s{XxGJkyDr|=dTyMtGised@eYxDzqakAM~O{XgEU{1?okCa~ z-)y_HIhpFFH$^Yhn2}+LRR1wFT4YHZX8g>zVwUnfd8-iO{De45dVOuCXX-aL6XYrf z_7vjaaE)l@X1{1&ac=^lWzRb zKE{cSAGoMUIuO15f+e-up5N5jF*~JMF zC$jD-0_KHkiAVmz&w{d=#}$O1!`U)}GqNS`6iTkxTF6&fmop+4Fkh>DsT+2IE3)?} z>QI1D1)WFwINS^0+7_OR-l*^o5gzNvg{fDcmFS{)sk12Sb(Gal*14Rz1kvkQ!dAUL zTvCESKSgQ~_UgubnWfjZodhJ?u~R%v@o|!*W93Hfatmg*WVF#h>Y&n>XZO`kQl)WX zDH-iGG~`AeQ}xrPLFy?4nd`0o#hbp3t*pS8?PzS(kXg$1WG4--s;SN4H&tp`=EfQm zfbdlMfGa4^X3P%4H!+&HYRpIHTMb$V$<|ls;qIL2C(SFN zT?32?*@MKYkm0Gkykkl)rHm;mQ8CiNFv)1&w^2R-Qs^*s%i$V69h}=a`S1z&bVO|` zn!|&RuyXVfK=b3quD?=*eH2LL;5fF#3`?P1xdZwVIQHWkbFDZvt5;b`zqnIE0 z8E=OWo^b&j=WrGt@PK9bdE!}#3wk=!I(+yIU=3}#U{g%(;NaVPa#!{CLMr zyt&d5eN34JRdxWxnbFyJB{Y@@Et{gZHAtl(61c5Ga2)f)IBhHmQtzgYeni`+qa!H* zy1q_S(QTE8TkY!6ojJE9fY`Bd$u5~Ap=12aNqH>Ka}z^k*+)R7~63!JKF zDHH<+`GiH${6`;t8RasIVx4X7t~PfmO&=Y-h|+S76&+1c<+>tRJVBU^+HUmw+7lpSH7Vzwyc+kspjN(R+xHfF~;T4iE6AiJSzfN;|@u|*N%G3sV zXUWZ4udrFm6uxLT1{$e+MFWJpF#$G__*ufXoN-((((QiYN$tumeceEF#*$ui5t%h7 zxr}8ya&g)QNcyxC&5KaDnFcYf8>0)9EPex-AuMf!qy}Oe_DIq}61J%sfi&pp z0N94TSOJhEaYYPwWY_pMgsxpsz)GA%m=%>NSsPGo{3)b94e$KK`l1x#O*hqb`r{}k zlTg(ge2}-%{n&=2bkSkNg=#K# zZkzrP5+U4bgSlImaB@7%r>aQ%0s5}-jfl02#(X2Hv(N0$ zVZ{?$K!l%j;zDjE=v?0Uc`a~3np&EcNY4(<$ZgabNz@?6V3CT?ke{>Tb=$y*#-OCo zXD!u1!zpX_SsiB7Q5+VGuPFfOlnJ%^x+3C1Oan87TnzHTh1oKH9JPH=tvcK`rIZb_ zW%f^%Gdnf8j3I2B49yyvJ;a^Fq3KrlUTP)b`sX#VArcyfBHuE< zS^Nh1&ErR+RAqtRcTC@qKSO_J>QBG^%+jCP`ZGs==IYNtUVo<_qf6g@<9n|oDgMt+U%^bqF%lsezJvS@=l6Pk@8_pUM~lwC z@Y|C(ql(z<5Kn=kGgrGEDUSE^FfiB(`Yl1No?{X7x!Qd$W_Nqc->6ryLa9CC0{=i( z6LOf~A-i9>csEXtSNpvSxN>qp6M+$xul?P{lR)1L5`_~yWaNpmbmcV@;7!ktOW5Vo zENn)bfVf%KvWu4MmA)r`Sf1xJrW^;;P8=={IfDIraftR~cH_$(wBm-S(>1Gy!l^qc z6CVW8@3dR?8Oo)X=>!@i#cuwX3piE*!XvJO3z>`C`6C%086H!u(Z%Mm2SEjwJ)}>j zv6YXFxzoY(Q+Q41<(cqz6sjkc=Gy%qaXFg3)Dq&z7M(v#cNHGnjEz>Yzi7kpv?*;{ zp7dBD5291qAr?P#7ENc|LLOzlB({Tr>na_>uP=*_Oglis(hHVZFINLx8p6|g`^ixUEx;F7@2 zs;oRWCUNWR<>zrl!11B(WZhxoDpS01*y9WW%T<-b-{Y4Zu32Z&{4JPwibVv z+gn^o4{MXj#g^rvm!I(}Sh_dFszyb^Xl-wHr% zycaVIJOa~sX{Y4+sN9*x&-7XUbN+6d-pM|Ko`On;f2S^FGtM`Aj&=tX=s$p(jkuKG zdsMpjDAt@!o#Kv;l<#zkv#INu5bvZ89NusthJ1%}NfK|payHq)I+cW)h)G31{-=)Q z_i=tt@LLT3t>-5L=sk4jSM&QcKi)4*&1E_HB7ST5y^9}*1F63;8z;DS_Pva_N%&$i zJu=Qef&KsT9bE%q>|3ebB6|eB!Y1)iG?>-l7m#u60?<)0K=Nbkl>DG5XtbmH z3Zr{WMpbP`^+loL_+!d*LG>WjJb|j=c2r*ys%xGjst-;^rC08o1(bkkw~C$%RlHt0 zk*bC5sJ<*z*FHz8{t16fC?F3ax2TfZ@uusZBdRMWqgvdKN@|w27JM!RJYh1bk#?!nk-)uO)NoteGrEY740?rNpKY4-{3B~?QFA$J^!NDM0w0Y7 zPqM%}+mZEx(+;;%uwBJjjWkuj1Y�h63;$!#Q+Omq}8 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ǚ|n$@FP#E-rTB?Ygp5n39C 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�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

phmfm6o~M}0xP#n+9B^BZ+=7H2;L<^g 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�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

l6uZ5i#8~iv?r_ffcmaSly+o?%ty4A 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Ï$ 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⁣&&G>V= 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

Exz<7!7uHihcr?9 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

|HOm`cEO0=6e$M*?ON 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 fPAmuY={uj_X$uUlB4> 2]; + Buffer.BlockCopy(data, offset, result, 0, byteLength); + return result; + } + + public static void InitializeArray(Array array, byte[] data, int offset, int length) + { + Buffer.BlockCopy(data, offset, array, 0, length); + } + + public static unsafe int CastFloatAsInt(float value) + { + int* intValue = (int*)&value; + return *intValue; + } + + public static unsafe float CastIntAsFloat(int value) + { + float* floatValue = (float*)&value; + return *floatValue; + } + + public static unsafe long CastDoubleAsLong(double value) + { + long* longValue = (long*)&value; + return *longValue; + } + + public static unsafe double CastLongAsDouble(long value) + { + double* doubleValue = (double*)&value; + return *doubleValue; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta new file mode 100644 index 00000000..f474205a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ConstUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48bd592d1a1339643be1fafe4b97c941 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs new file mode 100644 index 00000000..715853e6 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Obfuz +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + public class EncryptFieldAttribute : Attribute + { + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta new file mode 100644 index 00000000..a76ea9d1 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptFieldAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30f22110938816d4cb7e9cc9a176fd1e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs new file mode 100644 index 00000000..9e50ae8a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs @@ -0,0 +1,31 @@ +namespace Obfuz +{ + public interface IEncryptionScope + { + + } + + public abstract class EncryptionScopeBase : IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } + + public struct DefaultDynamicEncryptionScope : IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } + + public struct DefaultStaticEncryptionScope : IEncryptionScope + { + public void ForcePreserveAOT() + { + EncryptionService.Encrypt(0, 0, 0); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta new file mode 100644 index 00000000..064f8750 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d729fe7cb7d0bc43a69f1ba09f99061 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs new file mode 100644 index 00000000..546b05ed --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs @@ -0,0 +1,127 @@ +using System; + +namespace Obfuz +{ + + public static class EncryptionService where T : IEncryptionScope + { + // for compatibility with Mono because Mono will raise FieldAccessException when try access private field + public static IEncryptor _encryptor; + + public static IEncryptor Encryptor + { + get => _encryptor; + set { _encryptor = value; } + } + + public static void EncryptBlock(byte[] data, int ops, int salt) + { + _encryptor.EncryptBlock(data, ops, salt); + } + + public static void DecryptBlock(byte[] data, int ops, int salt) + { + _encryptor.DecryptBlock(data, ops, salt); + } + + public static int Encrypt(int value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static int Decrypt(int value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static long Encrypt(long value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static long Decrypt(long value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static float Encrypt(float value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static float Decrypt(float value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static double Encrypt(double value, int opts, int salt) + { + return _encryptor.Encrypt(value, opts, salt); + } + + public static double Decrypt(double value, int opts, int salt) + { + return _encryptor.Decrypt(value, opts, salt); + } + + public static byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt) + { + return _encryptor.Encrypt(value, offset, length, opts, salt); + } + + public static byte[] Decrypt(byte[] value, int offset, int byteLength, int ops, int salt) + { + return _encryptor.Decrypt(value, offset, byteLength, ops, salt); + } + + public static byte[] Encrypt(string value, int ops, int salt) + { + return _encryptor.Encrypt(value, ops, salt); + } + + public static string DecryptString(byte[] value, int offset, int stringBytesLength, int ops, int salt) + { + return _encryptor.DecryptString(value, offset, stringBytesLength, ops, salt); + } + + + public static int DecryptFromRvaInt(byte[] data, int offset, int ops, int salt) + { + int encryptedValue = BitConverter.ToInt32(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static long DecryptFromRvaLong(byte[] data, int offset, int ops, int salt) + { + long encryptedValue = BitConverter.ToInt64(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static float DecryptFromRvaFloat(byte[] data, int offset, int ops, int salt) + { + float encryptedValue = BitConverter.ToSingle(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static double DecryptFromRvaDouble(byte[] data, int offset, int ops, int salt) + { + double encryptedValue = BitConverter.ToDouble(data, offset); + return Decrypt(encryptedValue, ops, salt); + } + + public static string DecryptFromRvaString(byte[] data, int offset, int length, int ops, int salt) + { + return DecryptString(data, offset, length, ops, salt); + } + + public static byte[] DecryptFromRvaBytes(byte[] data, int offset, int bytesLength, int ops, int salt) + { + return Decrypt(data, offset, bytesLength, ops, salt); + } + + public static void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt) + { + _encryptor.DecryptInitializeArray(arr, field, length, ops, salt); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta new file mode 100644 index 00000000..d7364ef7 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptionService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbbeb7501a0d84542828cb1aa7103d1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs new file mode 100644 index 00000000..3fe2b305 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs @@ -0,0 +1,362 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.Assertions; + +namespace Obfuz +{ + public abstract class EncryptorBase : IEncryptor + { + public abstract int OpCodeCount { get; } + + public static int[] ConvertToIntKey(byte[] key) + { + Assert.AreEqual(0, key.Length % 4); + int align4Length = key.Length / 4; + int[] intKey = new int[align4Length]; + Buffer.BlockCopy(key, 0, intKey, 0, key.Length); + return intKey; + } + + public abstract int Encrypt(int value, int opts, int salt); + public abstract int Decrypt(int value, int opts, int salt); + + public virtual long Encrypt(long value, int opts, int salt) + { + int low = (int)value; + int high = (int)(value >> 32); + int encryptedLow = Encrypt(low, opts, salt); + int encryptedHigh = Encrypt(high, opts, salt); + return ((long)encryptedHigh << 32) | (uint)encryptedLow; + } + + public virtual long Decrypt(long value, int opts, int salt) + { + int low = (int)value; + int high = (int)(value >> 32); + int decryptedLow = Decrypt(low, opts, salt); + int decryptedHigh = Decrypt(high, opts, salt); + return ((long)decryptedHigh << 32) | (uint)decryptedLow; + } + + public virtual unsafe float Encrypt(float value, int opts, int salt) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + return value; + } + ref int intValue = ref *(int*)&value; + int xorValue = ((1 << 23) - 1) & Decrypt(0xABCD, opts, salt); + intValue ^= xorValue; + return value; + } + + public virtual unsafe float Decrypt(float value, int opts, int salt) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + return value; + } + ref int intValue = ref *(int*)&value; + int xorValue = ((1 << 23) - 1) & Decrypt(0xABCD, opts, salt); + intValue ^= xorValue; + return value; + } + + public virtual unsafe double Encrypt(double value, int opts, int salt) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + return value; + } + ref long longValue = ref *(long*)&value; + long xorValue = ((1L << 52) - 1) & Decrypt(0xAABBCCDDL, opts, salt); + longValue ^= xorValue; + return value; + } + + public virtual unsafe double Decrypt(double value, int opts, int salt) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + return value; + } + ref long longValue = ref *(long*)&value; + long xorValue = ((1L << 52) - 1) & Decrypt(0xAABBCCDDL, opts, salt); + longValue ^= xorValue; + return value; + } + + public virtual unsafe byte[] Encrypt(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + + var encryptedBytes = new byte[length]; + int intArrLength = length >> 2; + + // align to 4 + if ((offset & 0x3) != 0) + { + Buffer.BlockCopy(value, offset, encryptedBytes, 0, length); + + // encrypt int + + fixed (byte* dstBytePtr = &encryptedBytes[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(dstIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + for (int i = intArrLength * 4; i < length; i++) + { + encryptedBytes[i] = (byte)(encryptedBytes[i] ^ salt); + } + } + else + { + // encrypt int + fixed (byte* srcBytePtr = &value[offset]) + { + fixed (byte* dstBytePtr = &encryptedBytes[0]) + { + int* srcIntPtr = (int*)srcBytePtr; + int* dstIntPtr = (int*)dstBytePtr; + + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(srcIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + } + for (int i = intArrLength * 4; i < length; i++) + { + encryptedBytes[i] = (byte)(value[offset + i] ^ salt); + } + } + return encryptedBytes; + } + + public unsafe virtual byte[] Decrypt(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + var decryptedBytes = new byte[length]; + int intArrLength = length >> 2; + + // align to 4 + if ((offset & 0x3) != 0) + { + Buffer.BlockCopy(value, offset, decryptedBytes, 0, length); + + // encrypt int + + fixed (byte* dstBytePtr = &decryptedBytes[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = dstIntPtr[i]; + dstIntPtr[i] = Decrypt(last ^ oldLast, ops, salt); + } + } + for (int i = intArrLength * 4; i < length; i++) + { + decryptedBytes[i] = (byte)(decryptedBytes[i] ^ salt); + } + } + else + { + // encrypt int + fixed (byte* srcBytePtr = &value[offset]) + { + fixed (byte* dstBytePtr = &decryptedBytes[0]) + { + int* srcIntPtr = (int*)srcBytePtr; + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = srcIntPtr[i]; + dstIntPtr[i] = Decrypt(last ^ oldLast, ops, salt); + } + } + } + for (int i = intArrLength * 4; i < length; i++) + { + decryptedBytes[i] = (byte)(value[offset + i] ^ salt); + } + } + return decryptedBytes; + } + + public virtual byte[] Encrypt(string value, int ops, int salt) + { + if (value.Length == 0) + { + return Array.Empty(); + } + byte[] bytes = Encoding.UTF8.GetBytes(value); + return Encrypt(bytes, 0, bytes.Length, ops, salt); + } + + public virtual string DecryptString(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return string.Empty; + } + byte[] bytes = Decrypt(value, offset, length, ops, salt); + return Encoding.UTF8.GetString(bytes); + } + + public virtual unsafe void EncryptBlock(byte[] data, int ops, int salt) + { + int length = data.Length; + int intArrLength = length >> 2; + + fixed (byte* dstBytePtr = &data[0]) + { + int* dstIntPtr = (int*)dstBytePtr; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + last ^= Encrypt(dstIntPtr[i], ops, salt); + dstIntPtr[i] = last; + } + } + for (int i = intArrLength * 4; i < length; i++) + { + data[i] = (byte)(data[i] ^ salt); + } + } + + public virtual unsafe void DecryptBlock(byte[] data, int ops, int salt) + { + fixed (byte* dataPtr = &data[0]) + { + DecryptBlock(dataPtr, data.Length, ops, salt); + } + } + + private unsafe void DecryptBlock(byte* data, int length, int ops, int salt) + { + int intArrLength = length >> 2; + + int* dstIntPtr = (int*)data; + int last = 0; + for (int i = 0; i < intArrLength; i++) + { + int oldLast = last; + last = dstIntPtr[i]; + dstIntPtr[i] = Decrypt(oldLast ^ last, ops, salt); + } + for (int i = intArrLength * 4; i < length; i++) + { + data[i] = (byte)(data[i] ^ salt); + } + } + + public virtual unsafe void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt) + { + //Assert.AreEqual(Marshal.SizeOf(arr.GetType().GetElementType()), arr.Length); + RuntimeHelpers.InitializeArray(arr, field); + if (arr is byte[] byteArr) + { + fixed (byte* dataPtr = &byteArr[0]) + { + DecryptBlock(dataPtr, length, ops, salt); + } + } + else if (arr is int[] intArr) + { + fixed (int* dataPtr = &intArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is long[] longArr) + { + fixed (long* dataPtr = &longArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is sbyte[] sbyteArr) + { + fixed (sbyte* dataPtr = &sbyteArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is short[] shortArr) + { + fixed (short* dataPtr = &shortArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is ushort[] ushortArr) + { + fixed (ushort* dataPtr = &ushortArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is uint[] uintArr) + { + fixed (uint* dataPtr = &uintArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is ulong[] ulongArr) + { + fixed (ulong* dataPtr = &ulongArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is float[] floatArr) + { + fixed (float* dataPtr = &floatArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else if (arr is double[] doubleArr) + { + fixed (double* dataPtr = &doubleArr[0]) + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + } + else + { + void* dataPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(arr, out ulong handle); + try + { + DecryptBlock((byte*)dataPtr, length, ops, salt); + } + finally + { + UnsafeUtility.ReleaseGCObject(handle); + } + } + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta new file mode 100644 index 00000000..e81d5219 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/EncryptorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1d4c5725e7ad624ba8e55ecb63bb440 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs new file mode 100644 index 00000000..ff037e00 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs @@ -0,0 +1,247 @@ +using System; + +namespace Obfuz +{ + public static class ExprUtility + { + public static int Add(int a, int b) + { + return a + b; + } + + public static long Add(long a, long b) + { + return a + b; + } + + public static float Add(float a, float b) + { + return a + b; + } + + public static double Add(double a, double b) + { + return a + b; + } + + public static IntPtr Add(IntPtr a, IntPtr b) + { + return (IntPtr)((long)a + (long)b); + } + + public static IntPtr Add(IntPtr a, int b) + { + return a + b; + } + + public static int Subtract(int a, int b) + { + return a - b; + } + + public static long Subtract(long a, long b) + { + return a - b; + } + + public static float Subtract(float a, float b) + { + return a - b; + } + + public static double Subtract(double a, double b) + { + return a - b; + } + + public static IntPtr Subtract(IntPtr a, IntPtr b) + { + return (IntPtr)((long)a - (long)b); + } + + public static IntPtr Subtract(IntPtr a, int b) + { + return a - b; + } + + public static int Multiply(int a, int b) + { + return a * b; + } + + public static long Multiply(long a, long b) + { + return a * b; + } + + public static float Multiply(float a, float b) + { + return a * b; + } + + public static double Multiply(double a, double b) + { + return a * b; + } + + public static IntPtr Multiply(IntPtr a, IntPtr b) + { + return (IntPtr)((long)a * (long)b); + } + + public static IntPtr Multiply(IntPtr a, int b) + { + return (IntPtr)((long)a * b); + } + + public static int Divide(int a, int b) + { + return a / b; + } + + public static long Divide(long a, long b) + { + return a / b; + } + + public static float Divide(float a, float b) + { + return a / b; + } + + public static double Divide(double a, double b) + { + return a / b; + } + + public static int DivideUn(int a, int b) + { + return (int)((uint)a / (uint)b); + } + + public static long DivideUn(long a, long b) + { + return (long)((ulong)a / (ulong)b); + } + + public static int Rem(int a, int b) + { + return a % b; + } + + public static long Rem(long a, long b) + { + return a % b; + } + + public static float Rem(float a, float b) + { + return a % b; + } + + public static double Rem(double a, double b) + { + return a % b; + } + + public static int RemUn(int a, int b) + { + return (int)((uint)a % (uint)b); + } + + public static long RemUn(long a, long b) + { + return (long)((ulong)a % (ulong)b); + } + + public static int Negate(int a) + { + return -a; + } + + public static long Negate(long a) + { + return -a; + } + + public static float Negate(float a) + { + return -a; + } + + public static double Negate(double a) + { + return -a; + } + + public static int And(int a, int b) + { + return a & b; + } + + public static long And(long a, long b) + { + return a & b; + } + + public static int Or(int a, int b) + { + return a | b; + } + + public static long Or(long a, long b) + { + return a | b; + } + + public static int Xor(int a, int b) + { + return a ^ b; + } + + public static long Xor(long a, long b) + { + return a ^ b; + } + + public static int Not(int a) + { + return ~a; + } + + public static long Not(long a) + { + return ~a; + } + + public static int ShiftLeft(int a, int b) + { + return a << b; + } + + public static long ShiftLeft(long a, int b) + { + return a << b; + } + + public static int ShiftRight(int a, int b) + { + return a >> b; + } + + public static long ShiftRight(long a, int b) + { + return a >> b; + } + + public static int ShiftRightUn(int a, int b) + { + return (int)((uint)a >> b); + } + + public static long ShiftRightUn(long a, int b) + { + return (long)((ulong)a >> b); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs.meta new file mode 100644 index 00000000..4f7c95b8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ExprUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9aba5050818a0224696fcf73752fb225 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs new file mode 100644 index 00000000..63a609e9 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs @@ -0,0 +1,30 @@ +namespace Obfuz +{ + public interface IEncryptor + { + int OpCodeCount { get; } + + void EncryptBlock(byte[] data, int ops, int salt); + void DecryptBlock(byte[] data, int ops, int salt); + + int Encrypt(int value, int opts, int salt); + int Decrypt(int value, int opts, int salt); + + long Encrypt(long value, int opts, int salt); + long Decrypt(long value, int opts, int salt); + + float Encrypt(float value, int opts, int salt); + float Decrypt(float value, int opts, int salt); + + double Encrypt(double value, int opts, int salt); + double Decrypt(double value, int opts, int salt); + + byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt); + byte[] Decrypt(byte[] value, int offset, int byteLength, int ops, int salt); + + byte[] Encrypt(string value, int ops, int salt); + string DecryptString(byte[] value, int offset, int stringBytesLength, int ops, int salt); + + void DecryptInitializeArray(System.Array arr, System.RuntimeFieldHandle field, int length, int ops, int salt); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta new file mode 100644 index 00000000..41418218 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/IEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3078fa59ff0af6b4cbbee25e20bc41c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs new file mode 100644 index 00000000..20dca52c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; + +namespace Obfuz +{ + public class NullEncryptor : EncryptorBase + { + private readonly byte[] _key; + + public override int OpCodeCount => 256; + + public NullEncryptor(byte[] key) + { + _key = key; + } + + public override int Encrypt(int value, int opts, int salt) + { + return value; + } + + public override int Decrypt(int value, int opts, int salt) + { + return value; + } + + public override long Encrypt(long value, int opts, int salt) + { + return value; + } + + public override long Decrypt(long value, int opts, int salt) + { + return value; + } + + public override float Encrypt(float value, int opts, int salt) + { + return value; + } + + public override float Decrypt(float value, int opts, int salt) + { + return value; + } + + public override double Encrypt(double value, int opts, int salt) + { + return value; + } + + public override double Decrypt(double value, int opts, int salt) + { + return value; + } + + public override byte[] Encrypt(byte[] value, int offset, int length, int opts, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + var encryptedBytes = new byte[length]; + Buffer.BlockCopy(value, offset, encryptedBytes, 0, length); + return encryptedBytes; + } + + public override byte[] Decrypt(byte[] value, int offset, int length, int ops, int salt) + { + if (length == 0) + { + return Array.Empty(); + } + byte[] byteArr = new byte[length]; + Buffer.BlockCopy(value, 0, byteArr, 0, length); + return byteArr; + } + + public override byte[] Encrypt(string value, int ops, int salt) + { + return Encoding.UTF8.GetBytes(value); + } + + public override string DecryptString(byte[] value, int offset, int length, int ops, int salt) + { + return Encoding.UTF8.GetString(value, offset, length); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta new file mode 100644 index 00000000..b490d290 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/NullEncryptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c53481f2ec513be4783a5ae2f76dc6e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs new file mode 100644 index 00000000..45641f0a --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs @@ -0,0 +1,34 @@ +namespace Obfuz +{ + public static class ObfuscationInstincts + { + ///

+ /// Returns the original full name before obfuscated of the type T + /// + /// + /// + public static string FullNameOf() + { + return typeof(T).FullName; + } + + /// + /// Returns the original name before obfuscated of the type T + /// + /// + /// + public static string NameOf() + { + return typeof(T).Name; + } + + /// + /// register original type name to type mapping. + /// + /// + public static void RegisterReflectionType() + { + ObfuscationTypeMapper.RegisterType(typeof(T).FullName); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs.meta new file mode 100644 index 00000000..48d653ff --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationInstincts.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84320ab4adc4cbc49bf5e8f4009b4a96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs new file mode 100644 index 00000000..a85927ca --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Obfuz +{ + public static class ObfuscationTypeMapper + { + private static readonly Dictionary _type2OriginalFullName = new Dictionary(); + private static readonly Dictionary> _originalFullName2Types = new Dictionary>(); + + internal static void RegisterType(string originalFullName) + { + RegisterType(typeof(T), originalFullName); + } + + internal static void RegisterType(Type type, string originalFullName) + { + if (_type2OriginalFullName.ContainsKey(type)) + { + throw new ArgumentException($"Type '{type.FullName}' is already registered with original name '{_type2OriginalFullName[type]}'."); + } + _type2OriginalFullName.Add(type, originalFullName); + Assembly assembly = type.Assembly; + if (!_originalFullName2Types.TryGetValue(assembly, out var originalFullName2Types)) + { + originalFullName2Types = new Dictionary(); + _originalFullName2Types[assembly] = originalFullName2Types; + } + if (originalFullName2Types.ContainsKey(originalFullName)) + { + throw new ArgumentException($"Original full name '{originalFullName}' is already registered with type '{originalFullName2Types[originalFullName].FullName}'."); + } + originalFullName2Types.Add(originalFullName, type); + } + + public static string GetOriginalTypeFullName(Type type) + { + return _type2OriginalFullName.TryGetValue(type, out string originalFullName) + ? originalFullName + : throw new KeyNotFoundException($"Type '{type.FullName}' not found in the obfuscation mapping."); + } + + public static string GetOriginalTypeFullNameOrCurrent(Type type) + { + if (_type2OriginalFullName.TryGetValue(type, out string originalFullName)) + { + return originalFullName; + } + return type.FullName; + } + + public static Type GetTypeByOriginalFullName(Assembly assembly, string originalFullName) + { + if (_originalFullName2Types.TryGetValue(assembly, out var n2t)) + { + if (n2t.TryGetValue(originalFullName, out Type type)) + { + return type; + } + } + return null; + } + + public static void Clear() + { + _type2OriginalFullName.Clear(); + _originalFullName2Types.Clear(); + } + } + +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs.meta new file mode 100644 index 00000000..f496249d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuscationTypeMapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db6168acedd85984fa2c197fee1b0c15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef new file mode 100644 index 00000000..4ee1d16d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Obfuz.Runtime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta new file mode 100644 index 00000000..4868da27 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/Obfuz.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4140bd2e2764f1f47ab93125ecb61942 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs new file mode 100644 index 00000000..4aac1b88 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace Obfuz +{ + + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)] + public class ObfuzIgnoreAttribute : Attribute + { + public ObfuzScope Scope { get; set; } + + public bool ApplyToNestedTypes { get; set; } = true; + + public bool ApplyToChildTypes { get; set; } = false; + + public ObfuzIgnoreAttribute(ObfuzScope scope = ObfuzScope.All) + { + this.Scope = scope; + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta new file mode 100644 index 00000000..bc8fb89b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzIgnoreAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2b4cf04729157b4dab504167ab5f703 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs new file mode 100644 index 00000000..e3079900 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs @@ -0,0 +1,24 @@ +using System; + +namespace Obfuz +{ + [Flags] + public enum ObfuzScope + { + None = 0x0, + TypeName = 0x1, + Field = 0x2, + MethodName = 0x4, + MethodParameter = 0x8, + MethodBody = 0x10, + Method = MethodName | MethodParameter | MethodBody, + PropertyName = 0x20, + PropertyGetterSetterName = 0x40, + Property = PropertyName | PropertyGetterSetterName, + EventName = 0x100, + EventAddRemoveFireName = 0x200, + Event = EventName | PropertyGetterSetterName, + Module = 0x1000, + All = TypeName | Field | Method | Property | Event, + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs.meta new file mode 100644 index 00000000..1fec67df --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/Runtime/ObfuzScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c7e51fe12f206347b08a4b0be48605d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/package.json b/UnityProject/Packages/com.code-philosophy.obfuz/package.json new file mode 100644 index 00000000..28a66a96 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.code-philosophy.obfuz", + "version": "2.0.0", + "displayName": "Obfuz", + "description": "Obfuz is an open-source Unity code obfuscation tool designed to provide Unity developers with a powerful, secure, and user-friendly code protection solution.", + "category": "Scripting", + "documentationUrl": "https://www.obfuz.com", + "changelogUrl": "https://github.com/focus-creative-games/obfuz/commits/main/", + "licensesUrl": "https://github.com/focus-creative-games/obfuz/blob/main/com.code-philosophy.obfuz/LICENSE", + "keywords": [ + "obfuz", + "obfuscation", + "obfuscator", + "confuser", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "obfuz@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz/package.json.meta b/UnityProject/Packages/com.code-philosophy.obfuz/package.json.meta new file mode 100644 index 00000000..74168f7e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 63433d029d2e08c46abd56175e308a15 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor.meta new file mode 100644 index 00000000..ec942396 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 067341936b8cb2242be3bdc83f3ca3cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs new file mode 100644 index 00000000..c68a9237 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs @@ -0,0 +1,118 @@ +using dnlib.DotNet; +using dnlib.DotNet.PolymorphicWriter; +using HybridCLR.Editor; +using Obfuz; +using Obfuz.Settings; +using Obfuz.Unity; +using System; +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace Obfuz4HybridCLR +{ + public static class ObfuscateUtil + { + public static string PackageName { get; } = "com.code-philosophy.obfuz4hybridclr"; + + public static string TemplatePathInPackage => $"Packages/{PackageName}/Templates~"; + + public static bool AreSameDirectory(string path1, string path2) + { + try + { + var dir1 = new DirectoryInfo(path1); + var dir2 = new DirectoryInfo(path2); + + return dir1.FullName.TrimEnd('\\') == dir2.FullName.TrimEnd('\\'); + } + catch + { + return false; + } + } + + public static void ObfuscateHotUpdateAssemblies(BuildTarget target, string outputDir) + { + string hotUpdateDllPath = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target); + + AssemblySettings assemblySettings = ObfuzSettings.Instance.assemblySettings; + ObfuscationProcess.ValidateReferences(hotUpdateDllPath, new HashSet(assemblySettings.GetAssembliesToObfuscate()), new HashSet(assemblySettings.GetObfuscationRelativeAssemblyNames())); + var assemblySearchPaths = new List + { + hotUpdateDllPath, + }; + if (AreSameDirectory(hotUpdateDllPath, outputDir)) + { + throw new Exception($"hotUpdateDllPath:{hotUpdateDllPath} can't be same to outputDir:{outputDir}"); + } + Obfuscate(target, assemblySearchPaths, outputDir); + foreach (string hotUpdateAssemblyName in SettingsUtil.HotUpdateAssemblyNamesExcludePreserved) + { + string srcFile = $"{hotUpdateDllPath}/{hotUpdateAssemblyName}.dll"; + string dstFile = $"{outputDir}/{hotUpdateAssemblyName}.dll"; + // only copy non obfuscated assemblies + if (File.Exists(srcFile) && !File.Exists(dstFile)) + { + File.Copy(srcFile, dstFile, true); + Debug.Log($"[CompileAndObfuscateDll] Copy nonObfuscated assembly {srcFile} to {dstFile}"); + } + } + } + + public static void Obfuscate(BuildTarget target, List assemblySearchPaths, string obfuscatedAssemblyOutputPath) + { + var obfuzSettings = ObfuzSettings.Instance; + + var assemblySearchDirs = assemblySearchPaths; + ObfuscatorBuilder builder = ObfuscatorBuilder.FromObfuzSettings(obfuzSettings, target, true); + builder.InsertTopPriorityAssemblySearchPaths(assemblySearchDirs); + builder.CoreSettingsFacade.obfuscatedAssemblyOutputPath = obfuscatedAssemblyOutputPath; + + foreach (var assemblySearchDir in builder.CoreSettingsFacade.assemblySearchPaths) + { + if (AreSameDirectory(assemblySearchDir, obfuscatedAssemblyOutputPath)) + { + throw new Exception($"assemblySearchDir:{assemblySearchDir} can't be same to ObfuscatedAssemblyOutputPath:{obfuscatedAssemblyOutputPath}"); + } + } + + Obfuscator obfuz = builder.Build(); + obfuz.Run(); + } + + public static void GeneratePolymorphicDll(string originalDllPath, string outputDllPath) + { + ModuleDef oldMod = ModuleDefMD.Load(originalDllPath); + var obfuzSettings = ObfuzSettings.Instance; + + var opt = new NewDllModuleWriterOptions(oldMod) + { + MetadataWriter = new PolymorphicMetadataWriter(obfuzSettings.polymorphicDllSettings.codeGenerationSecretKey), + }; + PolymorphicModuleWriter writer = new PolymorphicModuleWriter(oldMod, opt); + writer.Write(outputDllPath); + Debug.Log($"GeneratePolymorphicDll {originalDllPath} => {outputDllPath}"); + } + + public static void GeneratePolymorphicCodes(string libil2cppDir) + { + PolymorphicDllSettings settings = ObfuzSettings.Instance.polymorphicDllSettings; + if (!settings.enable) + { + UnityEngine.Debug.LogWarning("Polymorphic code generation is disabled in Obfuz settings."); + return; + } + var options = new PolymorphicCodeGenerator.Options + { + GenerationSecretKey = settings.codeGenerationSecretKey, + Libil2cppDir = libil2cppDir, + TemplateDir = ObfuscateUtil.TemplatePathInPackage, + DisableLoadStandardDll = settings.disableLoadStandardDll, + }; + var generator = new PolymorphicCodeGenerator(options); + generator.Generate(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta new file mode 100644 index 00000000..3702fddd --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/ObfuscateUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7f5fe18513bcdd4c8960d908e88402e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef new file mode 100644 index 00000000..2af5cd54 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Obfuz4HybridCLR.Editor", + "rootNamespace": "", + "references": [ + "GUID:2373f786d14518f44b0f475db77ba4de", + "GUID:66e09fc524ec6594b8d6ca1d91aa1a41" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta new file mode 100644 index 00000000..cc375a6e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Obfuz4HybridCLR.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3743e71edcd5bd8499007797ef02cbfb +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic.meta new file mode 100644 index 00000000..6a8a5899 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52d353fb8d6d94c4aa03452a2cd9773f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs new file mode 100644 index 00000000..23d9c225 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using dnlib.DotNet.PolymorphicWriter; +using HybridCLR.Editor.Template; + +public class PolymorphicCodeGenerator +{ + public class Options + { + public string GenerationSecretKey { get; set; } + + public string Libil2cppDir { get; set; } + + public string TemplateDir { get; set; } + + public bool DisableLoadStandardDll { get; set; } = true; + } + + private readonly string _libil2cppDir; + private readonly string _metadataDir; + private readonly string _templateDir; + + private readonly string _generationSecretKey; + private readonly bool _disableLoadStandardImage; + + private readonly PolymorphicMetadataWriter writer; + + public PolymorphicCodeGenerator(Options options) + { + _libil2cppDir = options.Libil2cppDir; + _metadataDir = Path.Combine(_libil2cppDir, "hybridclr", "metadata"); + _templateDir = options.TemplateDir; + + _generationSecretKey = options.GenerationSecretKey; + _disableLoadStandardImage = options.DisableLoadStandardDll; + + writer = new PolymorphicMetadataWriter(_generationSecretKey); + } + + private void CopyMetadataReaderHeader() + { + string srcFile = $"{_templateDir}/MetadataReader.h.tpl"; + string dstFile = $"{_metadataDir}/MetadataReader.h"; + File.Copy(srcFile, dstFile, true); + UnityEngine.Debug.Log($"Copy MetadataReader header from {srcFile} to {dstFile}"); + } + + private void GeneratePolymorphicDefs() + { + string tplFile = $"{_templateDir}/PolymorphicDefs.h.tpl"; + var frr = new FileRegionReplace(File.ReadAllText(tplFile, Encoding.UTF8)); + var lines = new List(); + lines.Add($"#define POLYMORPHIC_IMAGE_SIGNATURE \"{writer.ImageSignature}\""); + lines.Add($"\tconstexpr uint32_t kPolymorphicImageVersion = {writer.FormatVersion};"); + lines.Add($"\tconstexpr uint32_t kFormatVariantVersion = {writer.FormatVariant};"); + string codes = string.Join("\n", lines); + frr.Replace("POLYMORPHIC_DEFINES", codes); + + string outputFile = $"{_metadataDir}/PolymorphicDefs.h"; + frr.Commit(outputFile); + } + + private void GeneratePolymorphicDatas() + { + string tplFile = $"{_templateDir}/PolymorphicDatas.h.tpl"; + var frr = new FileRegionReplace(File.ReadAllText(tplFile, Encoding.UTF8)); + List lines = new List(); + var sb = new StringBuilder(); + foreach (var type in writer.GetPolymorphicTypes()) + { + var polymorphicType = writer.GetPolymorphicClassDef(type); + lines.Add($"\tstruct {type.Name}"); + lines.Add("\t{"); + foreach (var field in polymorphicType.Fields) + { + lines.Add($"\t\t{field.fieldWriter.CppTypeName} {field.name};"); + } + + lines.Add("\t\tvoid Read(MetadataReader& reader)"); + lines.Add("\t\t{"); + + foreach (var field in polymorphicType.Fields) + { + lines.Add($"\t\t\t{field.fieldWriter.GetMarshalCode(field.name, "reader")};"); + } + lines.Add("\t\t}"); + lines.Add("\t};"); + lines.Add(""); + } + + string codes = string.Join("\n", lines); + frr.Replace("POLYMORPHIC_DATA", codes); + + + string outputFile = $"{_metadataDir}/PolymorphicDatas.h"; + frr.Commit(outputFile); + } + + private void GeneratePolymorphicRawImageHeader() + { + string tplFile = $"{_templateDir}/PolymorphicRawImage.h.tpl"; + var frr = new FileRegionReplace(File.ReadAllText(tplFile, Encoding.UTF8)); + + + var tableMetaInfoMap = TableMetaInfos.tableMetaInfos.ToDictionary(t => "Raw" + t.csharpTypeName + "Row"); + List lines = new List(); + foreach (Type rowType in writer.GetPolymorphicTableRowTypes()) + { + TableMetaInfo table = tableMetaInfoMap[rowType.Name]; + + lines.Add($"\t\tvirtual Tb{table.cppTypeName} Read{table.cppTypeName}(uint32_t rawIndex) override;"); + } + + frr.Replace("READ_TABLES_OVERRIDES", string.Join("\n", lines)); + + string outputFile = $"{_metadataDir}/PolymorphicRawImage.h"; + frr.Commit(outputFile); + } + + private void GeneratePolymorphicRawImageSource() + { + string tplFile = $"{_templateDir}/PolymorphicRawImage.cpp.tpl"; + var frr = new FileRegionReplace(File.ReadAllText(tplFile, Encoding.UTF8)); + + var tableMetaInfoMap = TableMetaInfos.tableMetaInfos.ToDictionary(t => "Raw" + t.csharpTypeName + "Row"); + { + List lines = new List(); + + foreach (Type rowType in writer.GetAllTableRowTypes()) + { + TableMetaInfo table = tableMetaInfoMap[rowType.Name]; + PolymorphicClassDef polymorphicClassDef = writer.CreateTableRowClassDefForCodeGeneration(rowType); + lines.Add("\t\t{"); + lines.Add($"\t\t\tauto& table = _tableRowMetas[(int)TableType::{table.cppEnumName}];"); + foreach (var fieldDef in polymorphicClassDef.Fields) + { + FieldMetaInfo field = table.fields.First(f => f.csharpName == fieldDef.name); + lines.Add($"\t\t\ttable.push_back({{{field.cppRowSize}}});"); + } + lines.Add("\t\t}"); + } + string codes = string.Join("\n", lines); + frr.Replace("TABLE_ROW_METADS", codes); + } + { + List lines = new List(); + foreach (Type rowType in writer.GetPolymorphicTableRowTypes()) + { + TableMetaInfo table = tableMetaInfoMap[rowType.Name]; + PolymorphicClassDef polymorphicClassDef = writer.CreateTableRowClassDefForCodeGeneration(rowType); + + lines.Add($"\tTb{table.cppTypeName} PolymorphicRawImage::Read{table.cppTypeName}(uint32_t rawIndex)"); + lines.Add("\t{"); + lines.Add($"\t\tIL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::{table.cppEnumName}).rowNum);"); + lines.Add($"\t\tconst byte* rowPtr = GetTableRowPtr(TableType::{table.cppEnumName}, rawIndex);"); + lines.Add($"\t\tauto& rowSchema = GetRowSchema(TableType::{table.cppEnumName});"); + lines.Add($"\t\tTb{table.cppTypeName} data;"); + for (int i = 0; i < polymorphicClassDef.Fields.Count; i++) + { + var fieldDef = polymorphicClassDef.Fields[i]; + FieldMetaInfo field = table.fields.First(f => f.csharpName == fieldDef.name); + lines.Add($"\t\tdata.{field.cppName} = ReadColumn(rowPtr, rowSchema[{i}]);"); + } + lines.Add("\t\treturn data;"); + lines.Add("\t}"); + } + + frr.Replace("READ_TABLES_IMPLEMENTATIONS", string.Join("\n", lines)); + } + string outputFile = $"{_metadataDir}/PolymorphicRawImage.cpp"; + frr.Commit(outputFile); + } + + private void GenerateRawImageInit() + { + string tplFile = $"{_metadataDir}/Image.cpp"; + var frr = new FileRegionReplace(File.ReadAllText(tplFile, Encoding.UTF8)); + + { + List lines = new List(); + lines.Add(@"#include ""PolymorphicRawImage.h"""); + + frr.Replace("INCLUDE_RAW_IMAGE_HEADERS", string.Join("\n", lines)); + } + { + List lines = new List(); + + lines.Add("\t\tif (std::strncmp((const char*)imageData, \"CODEPHPY\", 8) == 0)"); + lines.Add("\t\t{"); + lines.Add("\t\t\t_rawImage = new PolymorphicRawImage();"); + lines.Add("\t\t}"); + lines.Add("\t\telse"); + lines.Add("\t\t{"); + if (_disableLoadStandardImage) + { + lines.Add("\t\t\treturn LoadImageErrorCode::UNKNOWN_IMAGE_FORMAT;"); + } + else + { + lines.Add("\t\t\t_rawImage = new RawImage();"); + } + lines.Add("\t\t}"); + lines.Add("\t\treturn LoadImageErrorCode::OK;"); + + frr.Replace("INIT_RAW_IMAGE", string.Join("\n", lines)); + } + + + frr.Commit(tplFile); + } + + public void Generate() + { + CopyMetadataReaderHeader(); + GeneratePolymorphicDefs(); + GeneratePolymorphicDatas(); + GeneratePolymorphicRawImageHeader(); + GeneratePolymorphicRawImageSource(); + GenerateRawImageInit(); + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs.meta new file mode 100644 index 00000000..6d7baece --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/PolymorphicCodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b66b21680bfc8744682ea6536aa2ec77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs new file mode 100644 index 00000000..440cb7b8 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs @@ -0,0 +1,300 @@ +using System.Collections.Generic; + +class FieldMetaInfo { + public readonly string csharpName; + public readonly string cppName; + public readonly string cppRowSize; + public FieldMetaInfo(string csharpName, string cppName, string cppRowSize) { + this.csharpName = csharpName; + this.cppName = cppName; + this.cppRowSize = cppRowSize; + } + + public FieldMetaInfo(string csharpName, string cppRowSize) : this(csharpName, csharpName.Substring(0, 1).ToLower() + csharpName.Substring(1), cppRowSize) { + } +} + +class TableMetaInfo { + public readonly string csharpTypeName; + public readonly string cppTypeName; + public readonly string cppEnumName; + public readonly List fields; + + public TableMetaInfo(string csharpTypeName, string cppTypeName, string cppEnumName, List fields) { + this.csharpTypeName = csharpTypeName; + this.cppTypeName = cppTypeName; + this.cppEnumName = cppEnumName; + this.fields = fields; + } + + + public TableMetaInfo(string csharpTypeName, List fields) : this(csharpTypeName, csharpTypeName, csharpTypeName.ToUpper(), fields) { + } +} + +class TableMetaInfos { + public static readonly List tableMetaInfos = new List { + new TableMetaInfo("Module", new List { + new FieldMetaInfo("Generation", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Mvid", "ComputGUIDIndexByte()"), + new FieldMetaInfo("EncId", "ComputGUIDIndexByte()"), + new FieldMetaInfo("EncBaseId", "ComputGUIDIndexByte()"), + }), + new TableMetaInfo("TypeRef", new List { + new FieldMetaInfo("ResolutionScope", "ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope)"), + new FieldMetaInfo("Name", "typeName", "ComputStringIndexByte()"), + new FieldMetaInfo("Namespace", "typeNamespace", "ComputStringIndexByte()"), + }), + new TableMetaInfo("TypeDef", new List { + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("Name", "typeName", "ComputStringIndexByte()"), + new FieldMetaInfo("Namespace", "typeNamespace", "ComputStringIndexByte()"), + new FieldMetaInfo("Extends", "ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)"), + new FieldMetaInfo("FieldList", "ComputTableIndexByte(TableType::FIELD)"), + new FieldMetaInfo("MethodList", "ComputTableIndexByte(TableType::METHOD)"), + }), + new TableMetaInfo("FieldPtr", new List { + new FieldMetaInfo("Field", "ComputTableIndexByte(TableType::FIELD)"), + }), + new TableMetaInfo("Field", new List { + new FieldMetaInfo("Flags", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("MethodPtr", new List { + new FieldMetaInfo("Method", "ComputTableIndexByte(TableType::METHOD)"), + }), + new TableMetaInfo("Method", new List { + new FieldMetaInfo("RVA", "rva", "4"), + new FieldMetaInfo("ImplFlags", "2"), + new FieldMetaInfo("Flags", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + new FieldMetaInfo("ParamList", "ComputTableIndexByte(TableType::PARAM)"), + }), + new TableMetaInfo("ParamPtr", new List { + new FieldMetaInfo("Param", "ComputTableIndexByte(TableType::PARAM)"), + }), + new TableMetaInfo("Param", new List { + new FieldMetaInfo("Flags", "2"), + new FieldMetaInfo("Sequence", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + }), + new TableMetaInfo("InterfaceImpl", new List { + new FieldMetaInfo("Class", "classIdx", "ComputTableIndexByte(TableType::TYPEDEF)"), + new FieldMetaInfo("Interface", "interfaceIdx", "ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)"), + }), + new TableMetaInfo("MemberRef", new List { + new FieldMetaInfo("Class", "classIdx", "ComputTableIndexByte(TableType::METHOD, TableType::MODULEREF, TableType::TYPEDEF, TableType::TYPEREF, TagBits::MemberRefParent)"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("Constant", new List { + new FieldMetaInfo("Type", "1"), + new FieldMetaInfo("Padding", "1"), + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::PARAM, TableType::FIELD, TableType::PROPERTY, TagBits::HasConstant)"), + new FieldMetaInfo("Value", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("CustomAttribute", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(HasCustomAttributeAssociateTables, sizeof(HasCustomAttributeAssociateTables) / sizeof(TableType), TagBits::HasCustomAttribute)"), + new FieldMetaInfo("Type", "ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::CustomAttributeType)"), + new FieldMetaInfo("Value", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("FieldMarshal", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::FIELD, TableType::PARAM, TagBits::HasFieldMarshal)"), + new FieldMetaInfo("NativeType", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("DeclSecurity", new List { + new FieldMetaInfo("Action", "2"), + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::TYPEDEF, TableType::METHOD, TableType::ASSEMBLY, TagBits::HasDeclSecurity)"), + new FieldMetaInfo("PermissionSet", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("ClassLayout", new List { + new FieldMetaInfo("PackingSize", "2"), + new FieldMetaInfo("ClassSize", "4"), + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::TYPEDEF)"), + }), + new TableMetaInfo("FieldLayout", new List { + new FieldMetaInfo("OffSet", "offset", "4"), + new FieldMetaInfo("Field", "ComputTableIndexByte(TableType::FIELD)"), + }), + new TableMetaInfo("StandAloneSig", new List { + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("EventMap", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::TYPEDEF)"), + new FieldMetaInfo("EventList", "ComputTableIndexByte(TableType::EVENT)"), + }), + new TableMetaInfo("EventPtr", new List { + new FieldMetaInfo("Event", "ComputTableIndexByte(TableType::EVENT)"), + }), + new TableMetaInfo("Event", new List { + new FieldMetaInfo("EventFlags", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("EventType", "ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)"), + }), + new TableMetaInfo("PropertyMap", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::TYPEDEF)"), + new FieldMetaInfo("PropertyList", "ComputTableIndexByte(TableType::PROPERTY)"), + }), + new TableMetaInfo("PropertyPtr", new List { + new FieldMetaInfo("Property", "ComputTableIndexByte(TableType::PROPERTY)"), + }), + new TableMetaInfo("Property", new List { + new FieldMetaInfo("PropFlags", "flags", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Type", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("MethodSemantics", new List { + new FieldMetaInfo("Semantic", "semantics", "2"), + new FieldMetaInfo("Method", "ComputTableIndexByte(TableType::METHOD)"), + new FieldMetaInfo("Association", "ComputTableIndexByte(TableType::EVENT, TableType::PROPERTY, TagBits::HasSemantics)"), + }), + new TableMetaInfo("MethodImpl", new List { + new FieldMetaInfo("Class", "classIdx", "ComputTableIndexByte(TableType::TYPEDEF)"), + new FieldMetaInfo("MethodBody", "ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)"), + new FieldMetaInfo("MethodDeclaration", "ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)"), + }), + new TableMetaInfo("ModuleRef", new List { + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + }), + new TableMetaInfo("TypeSpec", new List { + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("ImplMap", new List { + new FieldMetaInfo("MappingFlags", "2"), + new FieldMetaInfo("MemberForwarded", "ComputTableIndexByte(TableType::FIELD, TableType::METHOD, TagBits::MemberForwarded)"), + new FieldMetaInfo("ImportName", "ComputStringIndexByte()"), + new FieldMetaInfo("ImportScope", "ComputTableIndexByte(TableType::MODULEREF)"), + }), + new TableMetaInfo("FieldRVA", new List { + new FieldMetaInfo("RVA", "rva", "4"), + new FieldMetaInfo("Field", "ComputTableIndexByte(TableType::FIELD)"), + }), + new TableMetaInfo("ENCLog","EncLog", "ENCLOG", new List { + new FieldMetaInfo("Token", "4"), + new FieldMetaInfo("FuncCode", "4"), + }), + new TableMetaInfo("ENCMap", "EncMap", "ENCMAP", new List { + new FieldMetaInfo("Token", "4"), + }), + new TableMetaInfo("Assembly", new List { + new FieldMetaInfo("HashAlgId", "4"), + new FieldMetaInfo("MajorVersion", "2"), + new FieldMetaInfo("MinorVersion", "2"), + new FieldMetaInfo("BuildNumber", "2"), + new FieldMetaInfo("RevisionNumber", "2"), + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("PublicKey", "ComputBlobIndexByte()"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Locale", "ComputStringIndexByte()"), + }), + new TableMetaInfo("AssemblyProcessor", new List { + new FieldMetaInfo("Processor", "4"), + }), + new TableMetaInfo("AssemblyOS", new List { + new FieldMetaInfo("OSPlatformId", "osPlatformId", "4"), + new FieldMetaInfo("OSMajorVersion", "osMajorVersion", "4"), + new FieldMetaInfo("OSMinorVersion", "osMinorVersion", "4"), + }), + new TableMetaInfo("AssemblyRef", new List { + new FieldMetaInfo("MajorVersion", "2"), + new FieldMetaInfo("MinorVersion", "2"), + new FieldMetaInfo("BuildNumber", "2"), + new FieldMetaInfo("RevisionNumber", "2"), + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("PublicKeyOrToken", "ComputBlobIndexByte()"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Locale", "ComputStringIndexByte()"), + new FieldMetaInfo("HashValue", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("AssemblyRefProcessor", new List { + new FieldMetaInfo("AssemblyRef", "4"), + new FieldMetaInfo("Processor", "ComputTableIndexByte(TableType::ASSEMBLYREF)"), + }), + new TableMetaInfo("AssemblyRefOS", new List { + new FieldMetaInfo("OSPlatformId", "osPlatformId", "4"), + new FieldMetaInfo("OSMajorVersion", "osMajorVersion", "4"), + new FieldMetaInfo("OSMinorVersion", "osMinorVersion", "4"), + new FieldMetaInfo("AssemblyRef", "ComputTableIndexByte(TableType::ASSEMBLYREF)"), + }), + new TableMetaInfo("File", new List { + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("HashValue", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("ExportedType", new List { + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("TypeDefId", "4"), + new FieldMetaInfo("TypeName", "ComputStringIndexByte()"), + new FieldMetaInfo("TypeNamespace", "ComputStringIndexByte()"), + new FieldMetaInfo("Implementation", "ComputTableIndexByte(TableType::FILE, TableType::EXPORTEDTYPE, TableType::ASSEMBLY, TagBits::Implementation)"), + }), + new TableMetaInfo("ManifestResource", new List { + new FieldMetaInfo("Offset", "4"), + new FieldMetaInfo("Flags", "4"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Implementation", "ComputTableIndexByte(TableType::FILE, TableType::ASSEMBLYREF, TagBits::Implementation)"), + }), + new TableMetaInfo("NestedClass", new List { + new FieldMetaInfo("NestedClass", "ComputTableIndexByte(TableType::TYPEDEF)"), + new FieldMetaInfo("EnclosingClass", "ComputTableIndexByte(TableType::TYPEDEF)"), + }), + new TableMetaInfo("GenericParam", new List { + new FieldMetaInfo("Number", "2"), + new FieldMetaInfo("Flags", "2"), + new FieldMetaInfo("Owner", "ComputTableIndexByte(TableType::TYPEDEF, TableType::METHOD, TagBits::TypeOrMethodDef)"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + }), + new TableMetaInfo("MethodSpec", new List { + new FieldMetaInfo("Method", "ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)"), + new FieldMetaInfo("Instantiation", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("GenericParamConstraint", new List { + new FieldMetaInfo("Owner", "ComputTableIndexByte(TableType::GENERICPARAM)"), + new FieldMetaInfo("Constraint", "ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)"), + }), + + new TableMetaInfo("Document", new List { + new FieldMetaInfo("Name", "ComputBlobIndexByte()"), + new FieldMetaInfo("HashAlgorithm", "ComputGUIDIndexByte()"), + new FieldMetaInfo("Hash", "ComputBlobIndexByte()"), + new FieldMetaInfo("Language", "ComputGUIDIndexByte()"), + }), + new TableMetaInfo("MethodDebugInformation", new List { + new FieldMetaInfo("Document", "ComputTableIndexByte(TableType::DOCUMENT)"), + new FieldMetaInfo("SequencePoints", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("LocalScope", new List { + new FieldMetaInfo("Method", "ComputTableIndexByte(TableType::METHOD)"), + new FieldMetaInfo("ImportScope", "ComputTableIndexByte(TableType::IMPORTSCOPE)"), + new FieldMetaInfo("VariableList", "variables", "ComputTableIndexByte(TableType::LOCALVARIABLE)"), + new FieldMetaInfo("ConstantList", "constants", "ComputTableIndexByte(TableType::LOCALCONSTANT)"), + new FieldMetaInfo("StartOffset", "4"), + new FieldMetaInfo("Length", "4"), + }), + new TableMetaInfo("LocalVariable", new List { + new FieldMetaInfo("Attributes", "2"), + new FieldMetaInfo("Index", "2"), + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + }), + new TableMetaInfo("LocalConstant", new List { + new FieldMetaInfo("Name", "ComputStringIndexByte()"), + new FieldMetaInfo("Signature", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("ImportScope", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(TableType::IMPORTSCOPE)"), + new FieldMetaInfo("Imports", "ComputBlobIndexByte()"), + }), + new TableMetaInfo("StateMachineMethod", new List { + new FieldMetaInfo("MoveNextMethod", "ComputTableIndexByte(TableType::METHOD)"), + new FieldMetaInfo("KickoffMethod", "ComputTableIndexByte(TableType::METHOD)"), + }), + new TableMetaInfo("CustomDebugInformation", new List { + new FieldMetaInfo("Parent", "ComputTableIndexByte(HasCustomDebugInformation, sizeof(HasCustomDebugInformation) / sizeof(TableType), TagBits::HasCustomDebugInformation)"), + new FieldMetaInfo("Kind", "ComputGUIDIndexByte()"), + new FieldMetaInfo("Value", "ComputBlobIndexByte()"), + }), + }; +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs.meta new file mode 100644 index 00000000..a3a2a88f --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/Polymorphic/TableMetaInfos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef98af767d086bd428f52503188789b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs new file mode 100644 index 00000000..85ce25b5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs @@ -0,0 +1,164 @@ +using HybridCLR.Editor.Commands; +using HybridCLR.Editor; +using Obfuz.Settings; +using Obfuz; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using System.Reflection; +using System; +using System.IO; +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Meta; +using UnityEditor.Build; +using HybridCLR.Editor.Installer; +using HybridCLR.Editor.MethodBridge; +using System.Linq; +using Analyzer = HybridCLR.Editor.MethodBridge.Analyzer; +using HybridCLR.Editor.Settings; +using Obfuz.Utils; +using FileUtil = Obfuz.Utils.FileUtil; +using IAssemblyResolver = HybridCLR.Editor.Meta.IAssemblyResolver; +using CombinedAssemblyResolver = HybridCLR.Editor.Meta.CombinedAssemblyResolver; +using MetaUtil = HybridCLR.Editor.Meta.MetaUtil; +using AssemblyCache = HybridCLR.Editor.Meta.AssemblyCache; +using HybridCLR.Editor.AOT; +using Analyzer2 = HybridCLR.Editor.AOT.Analyzer; + +namespace Obfuz4HybridCLR +{ + public static class PrebuildCommandExt + { + public static string GetObfuscatedHotUpdateAssemblyOutputPath(BuildTarget target) + { + return $"{ObfuzSettings.Instance.ObfuzRootDir}/{target}/ObfuscatedHotUpdateAssemblies"; + } + + + [MenuItem("HybridCLR/ObfuzExtension/GenerateAll")] + public static void GenerateAll() + { + var installer = new InstallerController(); + if (!installer.HasInstalledHybridCLR()) + { + throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'"); + } + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + Il2CppDefGeneratorCommand.GenerateIl2CppDef(); + GeneratePolymorphicCodesWhenEnable(); + LinkGeneratorCommand.GenerateLinkXml(target); + StripAOTDllCommand.GenerateStripedAOTDlls(target); + + string obfuscatedHotUpdateDllPath = GetObfuscatedHotUpdateAssemblyOutputPath(target); + ObfuscateUtil.ObfuscateHotUpdateAssemblies(target, obfuscatedHotUpdateDllPath); + GenerateMethodBridgeAndReversePInvokeWrapper(target, obfuscatedHotUpdateDllPath); + GenerateAOTGenericReference(target, obfuscatedHotUpdateDllPath); + } + + [MenuItem("HybridCLR/ObfuzExtension/CompileAndObfuscateDll")] + public static void CompileAndObfuscateDll() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + + string obfuscatedHotUpdateDllPath = GetObfuscatedHotUpdateAssemblyOutputPath(target); + ObfuscateUtil.ObfuscateHotUpdateAssemblies(target, obfuscatedHotUpdateDllPath); + } + + [MenuItem("HybridCLR/ObfuzExtension/GeneratePolymorphicCodes")] + public static void GeneratePolymorphicCodes() + { + ObfuscateUtil.GeneratePolymorphicCodes($"{SettingsUtil.LocalIl2CppDir}/libil2cpp"); + } + + private static void GeneratePolymorphicCodesWhenEnable() + { + PolymorphicDllSettings settings = ObfuzSettings.Instance.polymorphicDllSettings; + if (!settings.enable) + { + UnityEngine.Debug.LogWarning("Polymorphic code generation is disabled."); + return; + } + GeneratePolymorphicCodes(); + } + + public static IAssemblyResolver CreateObfuscatedHotUpdateAssemblyResolver(BuildTarget target, List obfuscatedHotUpdateAssemblies, string obfuscatedHotUpdateDllPath) + { + return new FixedSetAssemblyResolver(obfuscatedHotUpdateDllPath, obfuscatedHotUpdateAssemblies); + } + + public static IAssemblyResolver CreateObfuscatedHotUpdateAndAOTAssemblyResolver(BuildTarget target, List hotUpdateAssemblies, List assembliesToObfuscate, string obfuscatedHotUpdateDllPath) + { + return new CombinedAssemblyResolver( + CreateObfuscatedHotUpdateAssemblyResolver(target, hotUpdateAssemblies.Intersect(assembliesToObfuscate).ToList(), obfuscatedHotUpdateDllPath), + MetaUtil.CreateHotUpdateAssemblyResolver(target, hotUpdateAssemblies.Except(assembliesToObfuscate).ToList()), + MetaUtil.CreateAOTAssemblyResolver(target) + ); + } + + public static void GenerateMethodBridgeAndReversePInvokeWrapper(BuildTarget target, string obfuscatedHotUpdateDllPath) + { + string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + List aotAssemblyNames = Directory.Exists(aotDllDir) ? + Directory.GetFiles(aotDllDir, "*.dll", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToList() + : new List(); + if (aotAssemblyNames.Count == 0) + { + throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/MethodBridge`"); + } + AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames); + + var methodBridgeAnalyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(20, SettingsUtil.HybridCLRSettings.maxMethodBridgeGenericIteration), + Collector = collector, + }); + + methodBridgeAnalyzer.Run(); + + List hotUpdateDlls = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + var cache = new AssemblyCache(CreateObfuscatedHotUpdateAndAOTAssemblyResolver(target, hotUpdateDlls, ObfuzSettings.Instance.assemblySettings.GetAssembliesToObfuscate(), obfuscatedHotUpdateDllPath)); + + var reversePInvokeAnalyzer = new MonoPInvokeCallbackAnalyzer(cache, hotUpdateDlls); + reversePInvokeAnalyzer.Run(); + + var calliAnalyzer = new CalliAnalyzer(cache, hotUpdateDlls); + calliAnalyzer.Run(); + var pinvokeAnalyzer = new PInvokeAnalyzer(cache, hotUpdateDlls); + pinvokeAnalyzer.Run(); + var callPInvokeMethodSignatures = pinvokeAnalyzer.PInvokeMethodSignatures; + + string templateFile = $"{SettingsUtil.TemplatePathInPackage}/MethodBridge.cpp.tpl"; + string outputFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp"; + + var callNativeMethodSignatures = calliAnalyzer.CalliMethodSignatures.Concat(pinvokeAnalyzer.PInvokeMethodSignatures).ToList(); + + var generateMethodBridgeMethod = typeof(MethodBridgeGeneratorCommand).GetMethod("GenerateMethodBridgeCppFile", BindingFlags.NonPublic | BindingFlags.Static); + generateMethodBridgeMethod.Invoke(null, new object[] { methodBridgeAnalyzer.GenericMethods, reversePInvokeAnalyzer.ReversePInvokeMethods, callNativeMethodSignatures, templateFile, outputFile }); + + MethodBridgeGeneratorCommand.CleanIl2CppBuildCache(); + } + + public static void GenerateAOTGenericReference(BuildTarget target, string obfuscatedHotUpdateDllPath) + { + var gs = SettingsUtil.HybridCLRSettings; + List hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector( + CreateObfuscatedHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames, ObfuzSettings.Instance.assemblySettings.GetAssembliesToObfuscate(), obfuscatedHotUpdateDllPath), hotUpdateDllNames); + var analyzer = new Analyzer2(new Analyzer2.Options + { + MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration), + Collector = collector, + }); + + analyzer.Run(); + + var writer = new GenericReferenceWriter(); + writer.Write(analyzer.AotGenericTypes.ToList(), analyzer.AotGenericMethods.ToList(), $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}"); + AssetDatabase.Refresh(); + } + } +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta new file mode 100644 index 00000000..2bcd1c79 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Editor/PrebuildCommandExt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afc965e1afdfc8e47b8a70be7a93cf25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE new file mode 100644 index 00000000..093e5999 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Code Philosophy(代码哲学) + +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. diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE.meta new file mode 100644 index 00000000..dd09461e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3036602f815e31341b4445f0e331b58e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md new file mode 100644 index 00000000..d52e8ba0 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md @@ -0,0 +1,2 @@ +# obfuz4hybridclr +obfuz4hybridclr is a obfuz extension for HybridCLR. diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md.meta new file mode 100644 index 00000000..c8633d1e --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3ab25e7bb5a8e7d4fb3ba4614f2e3822 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/MetadataReader.h.tpl b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/MetadataReader.h.tpl new file mode 100644 index 00000000..0a630027 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/MetadataReader.h.tpl @@ -0,0 +1,96 @@ +#pragma once + +#include "MetadataUtil.h" + +namespace hybridclr +{ +namespace metadata +{ + struct ByteSpan + { + const byte* data; + uint32_t length; + ByteSpan() : data(nullptr), length(0) {} + ByteSpan(const byte* data, uint32_t length) : data(data), length(length) {} + }; + + class MetadataReader + { + private: + const byte* _data; + public: + MetadataReader(const byte* data) : _data(data) {} + int16_t ReadInt16() + { + int16_t value = GetI2LittleEndian(_data); + _data += 2; + return value; + } + + bool ReadBool() + { + return *(_data++) != 0; + } + + uint8_t ReadUInt8() + { + return *(_data++); + } + + uint16_t ReadUInt16() + { + uint16_t value = GetU2LittleEndian(_data); + _data += 2; + return value; + } + + int32_t ReadInt32() + { + int32_t value = GetI4LittleEndian(_data); + _data += 4; + return value; + } + + uint32_t ReadUInt32() + { + uint32_t value = GetU4LittleEndian(_data); + _data += 4; + return value; + } + + int64_t ReadInt64() + { + int64_t value = GetI8LittleEndian(_data); + _data += 8; + return value; + } + + uint64_t ReadUInt64() + { + uint64_t value = GetU8LittleEndian(_data); + _data += 8; + return value; + } + + const byte* ReadFixedBytes(int32_t byteCount) + { + const byte* value = _data; + _data += byteCount; + return value; + } + + ByteSpan ReadBytes() + { + uint32_t byteCount = ReadUInt32(); + const byte* buffer = _data; + _data += byteCount; + return ByteSpan(buffer, byteCount); + } + + const byte* CurrentDataPtr() const + { + return _data; + } + }; +} +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDatas.h.tpl b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDatas.h.tpl new file mode 100644 index 00000000..bb91b78b --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDatas.h.tpl @@ -0,0 +1,88 @@ +#pragma once +#include "MetadataReader.h" + +namespace hybridclr +{ +namespace metadata +{ + //!!!{{POLYMORPHIC_DATA + struct HeaderBaseData + { + uint32_t metadataSize; + uint32_t sectionCount; + const byte* dummyData; + uint32_t metadataRva; + uint32_t entryPointToken; + void Read(MetadataReader& reader) + { + metadataSize = reader.ReadUInt32(); + sectionCount = reader.ReadUInt32(); + dummyData = reader.ReadFixedBytes(8); + metadataRva = reader.ReadUInt32(); + entryPointToken = reader.ReadUInt32(); + } + }; + + struct SectionData + { + uint32_t rva; + uint32_t fileOffset; + uint32_t virtualSize; + uint32_t fileLength; + void Read(MetadataReader& reader) + { + rva = reader.ReadUInt32(); + fileOffset = reader.ReadUInt32(); + virtualSize = reader.ReadUInt32(); + fileLength = reader.ReadUInt32(); + } + }; + + struct MetadataHeaderBaseData + { + uint32_t signature; + uint8_t reserved2; + ByteSpan versionString; + uint16_t majorVersion; + uint16_t heapsCount; + uint32_t reserved1; + uint8_t storageFlags; + uint16_t minorVersion; + void Read(MetadataReader& reader) + { + signature = reader.ReadUInt32(); + reserved2 = reader.ReadUInt8(); + versionString = reader.ReadBytes(); + majorVersion = reader.ReadUInt16(); + heapsCount = reader.ReadUInt16(); + reserved1 = reader.ReadUInt32(); + storageFlags = reader.ReadUInt8(); + minorVersion = reader.ReadUInt16(); + } + }; + + struct TablesHeapHeaderBaseData + { + uint64_t validMask; + uint32_t reserved1; + uint8_t streamFlags; + uint8_t majorVersion; + uint64_t sortedMask; + uint8_t minorVersion; + uint8_t log2Rid; + void Read(MetadataReader& reader) + { + validMask = reader.ReadUInt64(); + reserved1 = reader.ReadUInt32(); + streamFlags = reader.ReadUInt8(); + majorVersion = reader.ReadUInt8(); + sortedMask = reader.ReadUInt64(); + minorVersion = reader.ReadUInt8(); + log2Rid = reader.ReadUInt8(); + } + }; + + + //!!!}}POLYMORPHIC_DATA +} +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDefs.h.tpl b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDefs.h.tpl new file mode 100644 index 00000000..a88b8a7d --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicDefs.h.tpl @@ -0,0 +1,28 @@ +#pragma once +#include "MetadataReader.h" + +namespace hybridclr +{ +namespace metadata +{ + //!!!{{POLYMORPHIC_DEFINES +#define POLYMORPHIC_IMAGE_SIGNATURE "CODEPHPY" + constexpr uint32_t kPolymorphicImageVersion = 1; + constexpr uint32_t kFormatVariantVersion = 0; + + //!!!}}POLYMORPHIC_DEFINES + + struct PolymorphicImageHeaderData + { + const byte* signature; + uint32_t formatVersion; + uint32_t formatVariant; + void Read(MetadataReader& reader) + { + signature = reader.ReadFixedBytes(8); + formatVersion = reader.ReadUInt32(); + formatVariant = reader.ReadUInt32(); + } + }; +} +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.cpp.tpl b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.cpp.tpl new file mode 100644 index 00000000..ef091f5c --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.cpp.tpl @@ -0,0 +1,897 @@ +#include "PolymorphicRawImage.h" + +#include + +#include "PolymorphicDefs.h" +#include "PolymorphicDatas.h" + +namespace hybridclr +{ +namespace metadata +{ + + struct RawSectionHeader + { + uint32_t fileOffset; + uint32_t fileLength; + uint32_t rva; + uint32_t virtualSize; + }; + + LoadImageErrorCode PolymorphicRawImage::LoadCLIHeader(uint32_t& entryPointToken, uint32_t& metadataRva, uint32_t& metadataSize) + { + if (_imageLength < 0x100) + { + return LoadImageErrorCode::BAD_IMAGE; + } + + MetadataReader reader(_imageData); + + PolymorphicImageHeaderData imageHeaderData = {}; + imageHeaderData.Read(reader); + + const char* sig = (const char*)_imageData; + if (std::strncmp((const char*)imageHeaderData.signature, POLYMORPHIC_IMAGE_SIGNATURE, sizeof(POLYMORPHIC_IMAGE_SIGNATURE) - 1)) + { + return LoadImageErrorCode::BAD_IMAGE; + } + if (imageHeaderData.formatVersion != kPolymorphicImageVersion) + { + return LoadImageErrorCode::UNSUPPORT_FORMAT_VERSION; + } + if (imageHeaderData.formatVariant != kFormatVariantVersion) + { + return LoadImageErrorCode::UNMATCH_FORMAT_VARIANT; + } + + //reader.ReadFixedBytes(polymorphic::kImageHeaderDummyDataSize); // Skip dummy data + + PolymorphicHeaderBaseData headerBaseData = {}; + headerBaseData.Read(reader); + + const size_t kEntryPointTokenOffset = 16; + entryPointToken = headerBaseData.entryPointToken; + metadataRva = headerBaseData.metadataRva; + metadataSize = headerBaseData.metadataSize; + + uint32_t sectionCount = headerBaseData.sectionCount; + for (uint32_t i = 0; i < sectionCount; i++) + { + PolymorphicSectionData sectionData = {}; + sectionData.Read(reader); + _sections.push_back({ sectionData.rva, sectionData.rva + sectionData.virtualSize, sectionData.fileOffset - sectionData.rva }); + } + return LoadImageErrorCode::OK; + } + + LoadImageErrorCode PolymorphicRawImage::LoadStreamHeaders(uint32_t metadataRva, uint32_t metadataSize) + { + uint32_t metaOffset; + if (!TranslateRVAToImageOffset(metadataRva, metaOffset)) + { + return LoadImageErrorCode::BAD_IMAGE; + } + if (metaOffset >= _imageLength) + { + return LoadImageErrorCode::BAD_IMAGE; + } + + const byte* ptrMetaData = _imageData + metaOffset; + MetadataReader reader(ptrMetaData); + + PolymorphicMetadataHeaderBaseData metadataHeader = {}; + metadataHeader.Read(reader); + if (metadataHeader.signature != 0x424A5342) + { + return LoadImageErrorCode::BAD_IMAGE; + } + + uint16_t numStreamHeader = metadataHeader.heapsCount; + const StreamHeader* ptrStreamHeaders = (const StreamHeader*)(reader.CurrentDataPtr()); + + const StreamHeader* curSH = ptrStreamHeaders; + const size_t maxStreamNameSize = 16; + for (int i = 0; i < numStreamHeader; i++) + { + //std::cout << "name:" << (char*)curSH->name << ", offset:" << curSH->offset << ", size:" << curSH->size << std::endl; + + if (curSH->offset >= metadataSize) + { + return LoadImageErrorCode::BAD_IMAGE; + } + CliStream* rs = nullptr; + CliStream nonStandardStream; + CliStream pdbStream; + if (!std::strncmp(curSH->name, "#~", maxStreamNameSize)) + { + rs = &_streamTables; + } + else if (!std::strncmp(curSH->name, "#Strings", maxStreamNameSize)) + { + rs = &_streamStringHeap; + } + else if (!std::strncmp(curSH->name, "#US", maxStreamNameSize)) + { + rs = &_streamUS; + } + else if (!std::strncmp(curSH->name, "#GUID", maxStreamNameSize)) + { + rs = &_streamGuidHeap; + if (curSH->size % 16 != 0) + { + return LoadImageErrorCode::BAD_IMAGE; + } + } + else if (!std::strncmp(curSH->name, "#Blob", maxStreamNameSize)) + { + rs = &_streamBlobHeap; + } + else if (!std::strncmp(curSH->name, "#-", maxStreamNameSize)) + { + rs = &nonStandardStream; + } + else if (!std::strncmp(curSH->name, "#Pdb", maxStreamNameSize)) + { + rs = &pdbStream; + } + else + { + //std::cerr << "unknown stream name:" << curSH->name << std::endl; + return LoadImageErrorCode::BAD_IMAGE; + } + rs->data = ptrMetaData + curSH->offset; + rs->size = curSH->size; + rs->name = curSH->name; + size_t sizeOfStream = 8 + (std::strlen(curSH->name) / 4 + 1) * 4; + curSH = (const StreamHeader*)((byte*)curSH + sizeOfStream); + } + return LoadImageErrorCode::OK; + } + + LoadImageErrorCode PolymorphicRawImage::LoadTables() + { + MetadataReader reader(_streamTables.data); + + PolymorphicTablesHeapHeaderBaseData heapHeader = {}; + heapHeader.Read(reader); + + if (heapHeader.reserved1 != 0 || heapHeader.majorVersion != 2 || heapHeader.minorVersion != 0) + { + return LoadImageErrorCode::BAD_IMAGE; + } + if ((heapHeader.streamFlags & ~0x7)) + { + return LoadImageErrorCode::BAD_IMAGE; + } + _4byteStringIndex = heapHeader.streamFlags & 0x1; + _4byteGUIDIndex = heapHeader.streamFlags & 0x2; + _4byteBlobIndex = heapHeader.streamFlags & 0x4; + + uint64_t validMask = ((uint64_t)1 << TABLE_NUM) - 1; + if (heapHeader.validMask & ~validMask) + { + return LoadImageErrorCode::BAD_IMAGE; + } + // sorted include not exist table, so check is not need. + //if (heapHeader.sorted & ~validMask) + //{ + // return LoadImageErrorCode::BAD_IMAGE; + //} + + uint32_t validTableNum = GetNotZeroBitCount(heapHeader.validMask); + //std::cout << "valid table num:" << validTableNum << std::endl; + //printf("#~ size:%0x\n", _streamTables.size); + const uint32_t* tableRowNums = (uint32_t*)(reader.CurrentDataPtr()); + const byte* tableDataBegin = (const byte*)(tableRowNums + validTableNum); + + { + int curValidTableIndex = 0; + for (int i = 0; i <= MAX_TABLE_INDEX; i++) + { + uint64_t mask = (uint64_t)1 << i; + _tables[i] = {}; + if (heapHeader.validMask & mask) + { + uint32_t rowNum = tableRowNums[curValidTableIndex]; + _tables[i].rowNum = rowNum; + ++curValidTableIndex; + } + } + } + + BuildTableRowMetas(); + + int curValidTableIndex = 0; + const byte* curTableData = tableDataBegin; + for (int i = 0; i <= MAX_TABLE_INDEX; i++) + { + uint64_t mask = (uint64_t)1 << i; + bool sorted = heapHeader.sortedMask & mask; + if (heapHeader.validMask & mask) + { + uint32_t rowNum = tableRowNums[curValidTableIndex]; + uint32_t totalSize = 0; + auto& table = _tableRowMetas[i]; + for (auto& col : table) + { + col.offset = totalSize; + totalSize += col.size; + } + uint32_t metaDataRowSize = totalSize; + //uint64_t offset = curTableData - _imageData; + _tables[i] = { curTableData, metaDataRowSize, rowNum, true, sorted }; + curTableData += metaDataRowSize * rowNum; + //std::cout << "table:" << i << " ," << curValidTableIndex << ", row_size:" << metaDataRowSize << ", row_num:" << rowNum << std::endl; + //printf("table:[%d][%d] offset:%0llx row_size:%d row_count:%d\n", i, curValidTableIndex, offset, metaDataRowSize, rowNum); + ++curValidTableIndex; + } + else + { + _tables[i] = { nullptr, 0, 0, false, sorted }; + } + } + + return LoadImageErrorCode::OK; + } + + void PolymorphicRawImage::BuildTableRowMetas() + { + //!!!{{TABLE_ROW_METADS + { + auto& table = _tableRowMetas[(int)TableType::MODULE]; + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputGUIDIndexByte()}); + table.push_back({ComputGUIDIndexByte()}); + table.push_back({ComputGUIDIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::TYPEREF]; + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::MODULE, TableType::MODULEREF, TableType::ASSEMBLYREF, TableType::TYPEREF, TagBits::ResoulutionScope)}); + } + { + auto& table = _tableRowMetas[(int)TableType::TYPEDEF]; + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::FIELD)}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)}); + table.push_back({4}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + } + { + auto& table = _tableRowMetas[(int)TableType::FIELDPTR]; + table.push_back({ComputTableIndexByte(TableType::FIELD)}); + } + { + auto& table = _tableRowMetas[(int)TableType::FIELD]; + table.push_back({ComputBlobIndexByte()}); + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHODPTR]; + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHOD]; + table.push_back({ComputBlobIndexByte()}); + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::PARAM)}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::PARAMPTR]; + table.push_back({ComputTableIndexByte(TableType::PARAM)}); + } + { + auto& table = _tableRowMetas[(int)TableType::PARAM]; + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({2}); + } + { + auto& table = _tableRowMetas[(int)TableType::INTERFACEIMPL]; + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)}); + } + { + auto& table = _tableRowMetas[(int)TableType::MEMBERREF]; + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::METHOD, TableType::MODULEREF, TableType::TYPEDEF, TableType::TYPEREF, TagBits::MemberRefParent)}); + } + { + auto& table = _tableRowMetas[(int)TableType::CONSTANT]; + table.push_back({1}); + table.push_back({1}); + table.push_back({ComputTableIndexByte(TableType::PARAM, TableType::FIELD, TableType::PROPERTY, TagBits::HasConstant)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::CUSTOMATTRIBUTE]; + table.push_back({ComputTableIndexByte(HasCustomAttributeAssociateTables, sizeof(HasCustomAttributeAssociateTables) / sizeof(TableType), TagBits::HasCustomAttribute)}); + table.push_back({ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::CustomAttributeType)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::FIELDMARSHAL]; + table.push_back({ComputTableIndexByte(TableType::FIELD, TableType::PARAM, TagBits::HasFieldMarshal)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::DECLSECURITY]; + table.push_back({2}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::METHOD, TableType::ASSEMBLY, TagBits::HasDeclSecurity)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::CLASSLAYOUT]; + table.push_back({4}); + table.push_back({2}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + } + { + auto& table = _tableRowMetas[(int)TableType::FIELDLAYOUT]; + table.push_back({ComputTableIndexByte(TableType::FIELD)}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::STANDALONESIG]; + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::EVENTMAP]; + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + table.push_back({ComputTableIndexByte(TableType::EVENT)}); + } + { + auto& table = _tableRowMetas[(int)TableType::EVENTPTR]; + table.push_back({ComputTableIndexByte(TableType::EVENT)}); + } + { + auto& table = _tableRowMetas[(int)TableType::EVENT]; + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)}); + table.push_back({ComputStringIndexByte()}); + table.push_back({2}); + } + { + auto& table = _tableRowMetas[(int)TableType::PROPERTYMAP]; + table.push_back({ComputTableIndexByte(TableType::PROPERTY)}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + } + { + auto& table = _tableRowMetas[(int)TableType::PROPERTYPTR]; + table.push_back({ComputTableIndexByte(TableType::PROPERTY)}); + } + { + auto& table = _tableRowMetas[(int)TableType::PROPERTY]; + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHODSEMANTICS]; + table.push_back({ComputTableIndexByte(TableType::EVENT, TableType::PROPERTY, TagBits::HasSemantics)}); + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + table.push_back({2}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHODIMPL]; + table.push_back({ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)}); + table.push_back({ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + } + { + auto& table = _tableRowMetas[(int)TableType::MODULEREF]; + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::TYPESPEC]; + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::IMPLMAP]; + table.push_back({2}); + table.push_back({ComputTableIndexByte(TableType::MODULEREF)}); + table.push_back({ComputTableIndexByte(TableType::FIELD, TableType::METHOD, TagBits::MemberForwarded)}); + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::FIELDRVA]; + table.push_back({ComputTableIndexByte(TableType::FIELD)}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ENCLOG]; + table.push_back({4}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ENCMAP]; + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLY]; + table.push_back({2}); + table.push_back({4}); + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + table.push_back({2}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLYPROCESSOR]; + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLYOS]; + table.push_back({4}); + table.push_back({4}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLYREF]; + table.push_back({4}); + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputBlobIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLYREFPROCESSOR]; + table.push_back({ComputTableIndexByte(TableType::ASSEMBLYREF)}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::ASSEMBLYREFOS]; + table.push_back({4}); + table.push_back({4}); + table.push_back({4}); + table.push_back({ComputTableIndexByte(TableType::ASSEMBLYREF)}); + } + { + auto& table = _tableRowMetas[(int)TableType::FILE]; + table.push_back({4}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::EXPORTEDTYPE]; + table.push_back({4}); + table.push_back({4}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::FILE, TableType::EXPORTEDTYPE, TableType::ASSEMBLY, TagBits::Implementation)}); + } + { + auto& table = _tableRowMetas[(int)TableType::MANIFESTRESOURCE]; + table.push_back({4}); + table.push_back({4}); + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputTableIndexByte(TableType::FILE, TableType::ASSEMBLYREF, TagBits::Implementation)}); + } + { + auto& table = _tableRowMetas[(int)TableType::NESTEDCLASS]; + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF)}); + } + { + auto& table = _tableRowMetas[(int)TableType::GENERICPARAM]; + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::METHOD, TagBits::TypeOrMethodDef)}); + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHODSPEC]; + table.push_back({ComputTableIndexByte(TableType::METHOD, TableType::MEMBERREF, TagBits::MethodDefOrRef)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::GENERICPARAMCONSTRAINT]; + table.push_back({ComputTableIndexByte(TableType::TYPEDEF, TableType::TYPEREF, TableType::TYPESPEC, TagBits::TypeDefOrRef)}); + table.push_back({ComputTableIndexByte(TableType::GENERICPARAM)}); + } + { + auto& table = _tableRowMetas[(int)TableType::DOCUMENT]; + table.push_back({ComputBlobIndexByte()}); + table.push_back({ComputGUIDIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + table.push_back({ComputGUIDIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::METHODDEBUGINFORMATION]; + table.push_back({ComputTableIndexByte(TableType::DOCUMENT)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::LOCALSCOPE]; + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + table.push_back({ComputTableIndexByte(TableType::IMPORTSCOPE)}); + table.push_back({ComputTableIndexByte(TableType::LOCALVARIABLE)}); + table.push_back({ComputTableIndexByte(TableType::LOCALCONSTANT)}); + table.push_back({4}); + table.push_back({4}); + } + { + auto& table = _tableRowMetas[(int)TableType::LOCALVARIABLE]; + table.push_back({2}); + table.push_back({2}); + table.push_back({ComputStringIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::LOCALCONSTANT]; + table.push_back({ComputStringIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::IMPORTSCOPE]; + table.push_back({ComputTableIndexByte(TableType::IMPORTSCOPE)}); + table.push_back({ComputBlobIndexByte()}); + } + { + auto& table = _tableRowMetas[(int)TableType::STATEMACHINEMETHOD]; + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + table.push_back({ComputTableIndexByte(TableType::METHOD)}); + } + { + auto& table = _tableRowMetas[(int)TableType::CUSTOMDEBUGINFORMATION]; + table.push_back({ComputTableIndexByte(HasCustomDebugInformation, sizeof(HasCustomDebugInformation) / sizeof(TableType), TagBits::HasCustomDebugInformation)}); + table.push_back({ComputGUIDIndexByte()}); + table.push_back({ComputBlobIndexByte()}); + } + + //!!!}}TABLE_ROW_METADS + + for (int i = 0; i < TABLE_NUM; i++) + { + auto& table = _tableRowMetas[i]; + if (table.empty()) + { + IL2CPP_ASSERT(_tables[i].rowNum == 0 && _tables[i].rowMetaDataSize == 0); + } + else + { + uint32_t totalSize = 0; + for (auto& col : table) + { + col.offset = totalSize; + totalSize += col.size; + } + uint32_t computSize = ComputTableRowMetaDataSize((TableType)i); + IL2CPP_ASSERT(totalSize == computSize); + } + } + } + + //!!!{{READ_TABLES_IMPLEMENTATIONS + TbTypeRef PolymorphicRawImage::ReadTypeRef(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::TYPEREF).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::TYPEREF, rawIndex); + auto& rowSchema = GetRowSchema(TableType::TYPEREF); + TbTypeRef data; + data.typeNamespace = ReadColumn(rowPtr, rowSchema[0]); + data.typeName = ReadColumn(rowPtr, rowSchema[1]); + data.resolutionScope = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbTypeDef PolymorphicRawImage::ReadTypeDef(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::TYPEDEF).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::TYPEDEF, rawIndex); + auto& rowSchema = GetRowSchema(TableType::TYPEDEF); + TbTypeDef data; + data.typeName = ReadColumn(rowPtr, rowSchema[0]); + data.fieldList = ReadColumn(rowPtr, rowSchema[1]); + data.extends = ReadColumn(rowPtr, rowSchema[2]); + data.flags = ReadColumn(rowPtr, rowSchema[3]); + data.typeNamespace = ReadColumn(rowPtr, rowSchema[4]); + data.methodList = ReadColumn(rowPtr, rowSchema[5]); + return data; + } + TbField PolymorphicRawImage::ReadField(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::FIELD).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::FIELD, rawIndex); + auto& rowSchema = GetRowSchema(TableType::FIELD); + TbField data; + data.signature = ReadColumn(rowPtr, rowSchema[0]); + data.flags = ReadColumn(rowPtr, rowSchema[1]); + data.name = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbMethod PolymorphicRawImage::ReadMethod(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::METHOD).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::METHOD, rawIndex); + auto& rowSchema = GetRowSchema(TableType::METHOD); + TbMethod data; + data.signature = ReadColumn(rowPtr, rowSchema[0]); + data.flags = ReadColumn(rowPtr, rowSchema[1]); + data.implFlags = ReadColumn(rowPtr, rowSchema[2]); + data.name = ReadColumn(rowPtr, rowSchema[3]); + data.paramList = ReadColumn(rowPtr, rowSchema[4]); + data.rva = ReadColumn(rowPtr, rowSchema[5]); + return data; + } + TbParam PolymorphicRawImage::ReadParam(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::PARAM).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::PARAM, rawIndex); + auto& rowSchema = GetRowSchema(TableType::PARAM); + TbParam data; + data.flags = ReadColumn(rowPtr, rowSchema[0]); + data.name = ReadColumn(rowPtr, rowSchema[1]); + data.sequence = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbInterfaceImpl PolymorphicRawImage::ReadInterfaceImpl(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::INTERFACEIMPL).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::INTERFACEIMPL, rawIndex); + auto& rowSchema = GetRowSchema(TableType::INTERFACEIMPL); + TbInterfaceImpl data; + data.classIdx = ReadColumn(rowPtr, rowSchema[0]); + data.interfaceIdx = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbMemberRef PolymorphicRawImage::ReadMemberRef(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::MEMBERREF).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::MEMBERREF, rawIndex); + auto& rowSchema = GetRowSchema(TableType::MEMBERREF); + TbMemberRef data; + data.name = ReadColumn(rowPtr, rowSchema[0]); + data.signature = ReadColumn(rowPtr, rowSchema[1]); + data.classIdx = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbConstant PolymorphicRawImage::ReadConstant(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::CONSTANT).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::CONSTANT, rawIndex); + auto& rowSchema = GetRowSchema(TableType::CONSTANT); + TbConstant data; + data.padding = ReadColumn(rowPtr, rowSchema[0]); + data.type = ReadColumn(rowPtr, rowSchema[1]); + data.parent = ReadColumn(rowPtr, rowSchema[2]); + data.value = ReadColumn(rowPtr, rowSchema[3]); + return data; + } + TbCustomAttribute PolymorphicRawImage::ReadCustomAttribute(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::CUSTOMATTRIBUTE).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::CUSTOMATTRIBUTE, rawIndex); + auto& rowSchema = GetRowSchema(TableType::CUSTOMATTRIBUTE); + TbCustomAttribute data; + data.parent = ReadColumn(rowPtr, rowSchema[0]); + data.type = ReadColumn(rowPtr, rowSchema[1]); + data.value = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbClassLayout PolymorphicRawImage::ReadClassLayout(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::CLASSLAYOUT).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::CLASSLAYOUT, rawIndex); + auto& rowSchema = GetRowSchema(TableType::CLASSLAYOUT); + TbClassLayout data; + data.classSize = ReadColumn(rowPtr, rowSchema[0]); + data.packingSize = ReadColumn(rowPtr, rowSchema[1]); + data.parent = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbFieldLayout PolymorphicRawImage::ReadFieldLayout(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::FIELDLAYOUT).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::FIELDLAYOUT, rawIndex); + auto& rowSchema = GetRowSchema(TableType::FIELDLAYOUT); + TbFieldLayout data; + data.field = ReadColumn(rowPtr, rowSchema[0]); + data.offset = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbStandAloneSig PolymorphicRawImage::ReadStandAloneSig(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::STANDALONESIG).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::STANDALONESIG, rawIndex); + auto& rowSchema = GetRowSchema(TableType::STANDALONESIG); + TbStandAloneSig data; + data.signature = ReadColumn(rowPtr, rowSchema[0]); + return data; + } + TbEventMap PolymorphicRawImage::ReadEventMap(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::EVENTMAP).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::EVENTMAP, rawIndex); + auto& rowSchema = GetRowSchema(TableType::EVENTMAP); + TbEventMap data; + data.parent = ReadColumn(rowPtr, rowSchema[0]); + data.eventList = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbEvent PolymorphicRawImage::ReadEvent(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::EVENT).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::EVENT, rawIndex); + auto& rowSchema = GetRowSchema(TableType::EVENT); + TbEvent data; + data.eventType = ReadColumn(rowPtr, rowSchema[0]); + data.name = ReadColumn(rowPtr, rowSchema[1]); + data.eventFlags = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbPropertyMap PolymorphicRawImage::ReadPropertyMap(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::PROPERTYMAP).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::PROPERTYMAP, rawIndex); + auto& rowSchema = GetRowSchema(TableType::PROPERTYMAP); + TbPropertyMap data; + data.propertyList = ReadColumn(rowPtr, rowSchema[0]); + data.parent = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbProperty PolymorphicRawImage::ReadProperty(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::PROPERTY).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::PROPERTY, rawIndex); + auto& rowSchema = GetRowSchema(TableType::PROPERTY); + TbProperty data; + data.flags = ReadColumn(rowPtr, rowSchema[0]); + data.name = ReadColumn(rowPtr, rowSchema[1]); + data.type = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbMethodSemantics PolymorphicRawImage::ReadMethodSemantics(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::METHODSEMANTICS).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::METHODSEMANTICS, rawIndex); + auto& rowSchema = GetRowSchema(TableType::METHODSEMANTICS); + TbMethodSemantics data; + data.association = ReadColumn(rowPtr, rowSchema[0]); + data.method = ReadColumn(rowPtr, rowSchema[1]); + data.semantics = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbMethodImpl PolymorphicRawImage::ReadMethodImpl(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::METHODIMPL).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::METHODIMPL, rawIndex); + auto& rowSchema = GetRowSchema(TableType::METHODIMPL); + TbMethodImpl data; + data.methodDeclaration = ReadColumn(rowPtr, rowSchema[0]); + data.methodBody = ReadColumn(rowPtr, rowSchema[1]); + data.classIdx = ReadColumn(rowPtr, rowSchema[2]); + return data; + } + TbModuleRef PolymorphicRawImage::ReadModuleRef(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::MODULEREF).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::MODULEREF, rawIndex); + auto& rowSchema = GetRowSchema(TableType::MODULEREF); + TbModuleRef data; + data.name = ReadColumn(rowPtr, rowSchema[0]); + return data; + } + TbTypeSpec PolymorphicRawImage::ReadTypeSpec(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::TYPESPEC).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::TYPESPEC, rawIndex); + auto& rowSchema = GetRowSchema(TableType::TYPESPEC); + TbTypeSpec data; + data.signature = ReadColumn(rowPtr, rowSchema[0]); + return data; + } + TbImplMap PolymorphicRawImage::ReadImplMap(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::IMPLMAP).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::IMPLMAP, rawIndex); + auto& rowSchema = GetRowSchema(TableType::IMPLMAP); + TbImplMap data; + data.mappingFlags = ReadColumn(rowPtr, rowSchema[0]); + data.importScope = ReadColumn(rowPtr, rowSchema[1]); + data.memberForwarded = ReadColumn(rowPtr, rowSchema[2]); + data.importName = ReadColumn(rowPtr, rowSchema[3]); + return data; + } + TbFieldRVA PolymorphicRawImage::ReadFieldRVA(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::FIELDRVA).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::FIELDRVA, rawIndex); + auto& rowSchema = GetRowSchema(TableType::FIELDRVA); + TbFieldRVA data; + data.field = ReadColumn(rowPtr, rowSchema[0]); + data.rva = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbAssembly PolymorphicRawImage::ReadAssembly(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::ASSEMBLY).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::ASSEMBLY, rawIndex); + auto& rowSchema = GetRowSchema(TableType::ASSEMBLY); + TbAssembly data; + data.minorVersion = ReadColumn(rowPtr, rowSchema[0]); + data.hashAlgId = ReadColumn(rowPtr, rowSchema[1]); + data.buildNumber = ReadColumn(rowPtr, rowSchema[2]); + data.revisionNumber = ReadColumn(rowPtr, rowSchema[3]); + data.locale = ReadColumn(rowPtr, rowSchema[4]); + data.name = ReadColumn(rowPtr, rowSchema[5]); + data.publicKey = ReadColumn(rowPtr, rowSchema[6]); + data.majorVersion = ReadColumn(rowPtr, rowSchema[7]); + data.flags = ReadColumn(rowPtr, rowSchema[8]); + return data; + } + TbAssemblyRef PolymorphicRawImage::ReadAssemblyRef(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::ASSEMBLYREF).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::ASSEMBLYREF, rawIndex); + auto& rowSchema = GetRowSchema(TableType::ASSEMBLYREF); + TbAssemblyRef data; + data.flags = ReadColumn(rowPtr, rowSchema[0]); + data.majorVersion = ReadColumn(rowPtr, rowSchema[1]); + data.buildNumber = ReadColumn(rowPtr, rowSchema[2]); + data.publicKeyOrToken = ReadColumn(rowPtr, rowSchema[3]); + data.hashValue = ReadColumn(rowPtr, rowSchema[4]); + data.revisionNumber = ReadColumn(rowPtr, rowSchema[5]); + data.minorVersion = ReadColumn(rowPtr, rowSchema[6]); + data.locale = ReadColumn(rowPtr, rowSchema[7]); + data.name = ReadColumn(rowPtr, rowSchema[8]); + return data; + } + TbNestedClass PolymorphicRawImage::ReadNestedClass(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::NESTEDCLASS).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::NESTEDCLASS, rawIndex); + auto& rowSchema = GetRowSchema(TableType::NESTEDCLASS); + TbNestedClass data; + data.enclosingClass = ReadColumn(rowPtr, rowSchema[0]); + data.nestedClass = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbGenericParam PolymorphicRawImage::ReadGenericParam(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::GENERICPARAM).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::GENERICPARAM, rawIndex); + auto& rowSchema = GetRowSchema(TableType::GENERICPARAM); + TbGenericParam data; + data.flags = ReadColumn(rowPtr, rowSchema[0]); + data.number = ReadColumn(rowPtr, rowSchema[1]); + data.owner = ReadColumn(rowPtr, rowSchema[2]); + data.name = ReadColumn(rowPtr, rowSchema[3]); + return data; + } + TbMethodSpec PolymorphicRawImage::ReadMethodSpec(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::METHODSPEC).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::METHODSPEC, rawIndex); + auto& rowSchema = GetRowSchema(TableType::METHODSPEC); + TbMethodSpec data; + data.method = ReadColumn(rowPtr, rowSchema[0]); + data.instantiation = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + TbGenericParamConstraint PolymorphicRawImage::ReadGenericParamConstraint(uint32_t rawIndex) + { + IL2CPP_ASSERT(rawIndex > 0 && rawIndex <= GetTable(TableType::GENERICPARAMCONSTRAINT).rowNum); + const byte* rowPtr = GetTableRowPtr(TableType::GENERICPARAMCONSTRAINT, rawIndex); + auto& rowSchema = GetRowSchema(TableType::GENERICPARAMCONSTRAINT); + TbGenericParamConstraint data; + data.constraint = ReadColumn(rowPtr, rowSchema[0]); + data.owner = ReadColumn(rowPtr, rowSchema[1]); + return data; + } + + //!!!}}READ_TABLES_IMPLEMENTATIONS +} +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.h.tpl b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.h.tpl new file mode 100644 index 00000000..a48517b5 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/Templates~/PolymorphicRawImage.h.tpl @@ -0,0 +1,58 @@ +#pragma once + +#include "DotNetRawImageBase.h" + +namespace hybridclr +{ +namespace metadata +{ + + class PolymorphicRawImage : public DotNetRawImageBase + { + public: + PolymorphicRawImage() : DotNetRawImageBase() + { + + } + + LoadImageErrorCode LoadCLIHeader(uint32_t& entryPointToken, uint32_t& metadataRva, uint32_t& metadataSize) override; + virtual LoadImageErrorCode LoadStreamHeaders(uint32_t metadataRva, uint32_t metadataSize) override; + virtual LoadImageErrorCode LoadTables() override; + virtual void BuildTableRowMetas() override; + + //!!!{{READ_TABLES_OVERRIDES + virtual TbTypeRef ReadTypeRef(uint32_t rawIndex) override; + virtual TbTypeDef ReadTypeDef(uint32_t rawIndex) override; + virtual TbField ReadField(uint32_t rawIndex) override; + virtual TbMethod ReadMethod(uint32_t rawIndex) override; + virtual TbParam ReadParam(uint32_t rawIndex) override; + virtual TbInterfaceImpl ReadInterfaceImpl(uint32_t rawIndex) override; + virtual TbMemberRef ReadMemberRef(uint32_t rawIndex) override; + virtual TbConstant ReadConstant(uint32_t rawIndex) override; + virtual TbCustomAttribute ReadCustomAttribute(uint32_t rawIndex) override; + virtual TbClassLayout ReadClassLayout(uint32_t rawIndex) override; + virtual TbFieldLayout ReadFieldLayout(uint32_t rawIndex) override; + virtual TbStandAloneSig ReadStandAloneSig(uint32_t rawIndex) override; + virtual TbEventMap ReadEventMap(uint32_t rawIndex) override; + virtual TbEvent ReadEvent(uint32_t rawIndex) override; + virtual TbPropertyMap ReadPropertyMap(uint32_t rawIndex) override; + virtual TbProperty ReadProperty(uint32_t rawIndex) override; + virtual TbMethodSemantics ReadMethodSemantics(uint32_t rawIndex) override; + virtual TbMethodImpl ReadMethodImpl(uint32_t rawIndex) override; + virtual TbModuleRef ReadModuleRef(uint32_t rawIndex) override; + virtual TbTypeSpec ReadTypeSpec(uint32_t rawIndex) override; + virtual TbImplMap ReadImplMap(uint32_t rawIndex) override; + virtual TbFieldRVA ReadFieldRVA(uint32_t rawIndex) override; + virtual TbAssembly ReadAssembly(uint32_t rawIndex) override; + virtual TbAssemblyRef ReadAssemblyRef(uint32_t rawIndex) override; + virtual TbNestedClass ReadNestedClass(uint32_t rawIndex) override; + virtual TbGenericParam ReadGenericParam(uint32_t rawIndex) override; + virtual TbMethodSpec ReadMethodSpec(uint32_t rawIndex) override; + virtual TbGenericParamConstraint ReadGenericParamConstraint(uint32_t rawIndex) override; + + //!!!}}READ_TABLES_OVERRIDES + private: + + }; +} +} diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json new file mode 100644 index 00000000..290afcd3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.code-philosophy.obfuz4hybridclr", + "version": "1.0.0-beta.1", + "displayName": "Obfuz4HybridCLR", + "description": "Obfuz4HybridCLR is a obfuz extension for HybridCLR", + "category": "Scripting", + "documentationUrl": "https://www.obfuz.com", + "changelogUrl": "https://github.com/focus-creative-games/obfuz/commits/main/", + "licensesUrl": "https://github.com/focus-creative-games/obfuz/blob/main/com.code-philosophy.obfuz4hybridclr/LICENSE", + "keywords": [ + "obfuz", + "obfuscation", + "obfuscator", + "confuser", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "obfuz@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json.meta b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json.meta new file mode 100644 index 00000000..5577b3b3 --- /dev/null +++ b/UnityProject/Packages/com.code-philosophy.obfuz4hybridclr/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9ac66e213a764b840b2533ee30123717 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Packages/manifest.json b/UnityProject/Packages/manifest.json index c0d35478..f2dda69d 100644 --- a/UnityProject/Packages/manifest.json +++ b/UnityProject/Packages/manifest.json @@ -1,6 +1,5 @@ { "dependencies": { - "com.code-philosophy.hybridclr": "https://gitee.com/focus-creative-games/hybridclr_unity.git", "com.unity.ide.rider": "3.0.34", "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", diff --git a/UnityProject/Packages/packages-lock.json b/UnityProject/Packages/packages-lock.json index d73ccf1f..069ff254 100644 --- a/UnityProject/Packages/packages-lock.json +++ b/UnityProject/Packages/packages-lock.json @@ -1,11 +1,22 @@ { "dependencies": { "com.code-philosophy.hybridclr": { - "version": "https://gitee.com/focus-creative-games/hybridclr_unity.git", + "version": "file:com.code-philosophy.hybridclr", "depth": 0, - "source": "git", - "dependencies": {}, - "hash": "ee7af5c5926c12402b3ccd121408646f1cf3128f" + "source": "embedded", + "dependencies": {} + }, + "com.code-philosophy.obfuz": { + "version": "file:com.code-philosophy.obfuz", + "depth": 0, + "source": "embedded", + "dependencies": {} + }, + "com.code-philosophy.obfuz4hybridclr": { + "version": "file:com.code-philosophy.obfuz4hybridclr", + "depth": 0, + "source": "embedded", + "dependencies": {} }, "com.cysharp.unitask": { "version": "file:UniTask", diff --git a/UnityProject/ProjectSettings/Obfuz.asset b/UnityProject/ProjectSettings/Obfuz.asset new file mode 100644 index 00000000..560de562 --- /dev/null +++ b/UnityProject/ProjectSettings/Obfuz.asset @@ -0,0 +1,85 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c414eef017e565c4db1442ec64ec52fe, type: 3} + m_Name: + m_EditorClassIdentifier: + buildPipelineSettings: + enable: 1 + linkXmlProcessCallbackOrder: 10000 + obfuscationProcessCallbackOrder: 10000 + assemblySettings: + assembliesToObfuscate: + - TEngine.Runtime + - Assembly-CSharp + nonObfuscatedButReferencingObfuscatedAssemblies: + - Launcher + additionalAssemblySearchPaths: [] + obfuscateObfuzRuntime: 1 + obfuscationPassSettings: + enabledPasses: -1 + ruleFiles: [] + secretSettings: + defaultStaticSecretKey: Code Philosophy-Static + defaultDynamicSecretKey: Code Philosophy-Dynamic + staticSecretKeyOutputPath: Assets/Resources/Obfuz/defaultStaticSecretKey.bytes + dynamicSecretKeyOutputPath: Assets/Resources/Obfuz/defaultDynamicSecretKey.bytes + randomSeed: 0 + assembliesUsingDynamicSecretKeys: [] + encryptionVMSettings: + codeGenerationSecretKey: Obfuz + encryptionOpCodeCount: 256 + codeOutputPath: Assets/Obfuz/GeneratedEncryptionVirtualMachine.cs + symbolObfusSettings: + debug: 0 + obfuscatedNamePrefix: $ + useConsistentNamespaceObfuscation: 1 + detectReflectionCompatibility: 1 + keepUnknownSymbolInSymbolMappingFile: 1 + symbolMappingFile: Assets/Obfuz/SymbolObfus/symbol-mapping.xml + debugSymbolMappingFile: Assets/Obfuz/SymbolObfus/symbol-mapping-debug.xml + ruleFiles: [] + customRenamePolicyTypes: [] + constEncryptSettings: + encryptionLevel: 1 + ruleFiles: [] + evalStackObfusSettings: + ruleFiles: [] + fieldEncryptSettings: + encryptionLevel: 1 + ruleFiles: [] + callObfusSettings: + proxyMode: 0 + obfuscationLevel: 1 + maxProxyMethodCountPerDispatchMethod: 100 + obfuscateCallToMethodInMscorlib: 0 + ruleFiles: [] + exprObfusSettings: + ruleFiles: [] + controlFlowObfusSettings: + minInstructionCountOfBasicBlockToObfuscate: 3 + ruleFiles: [] + garbageCodeGenerationSettings: + codeGenerationSecret: Garbage Code + defaultTask: + codeGenerationRandomSeed: 0 + classNamespace: __GarbageCode + classNamePrefix: __GeneratedGarbageClass + classCount: 100 + methodCountPerClass: 10 + fieldCountPerClass: 50 + garbageCodeType: 1 + outputPath: Assets/Obfuz/GarbageCode + additionalTasks: [] + polymorphicDllSettings: + enable: 1 + codeGenerationSecretKey: obfuz-polymorphic-key + disableLoadStandardDll: 0