├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── PortableMetadata.Benchmarks ├── PortableMetadata.Benchmarks.csproj └── Program.cs ├── PortableMetadata.Dnlib ├── PortableMetadata.Dnlib.csproj ├── PortableMetadataReader.cs └── PortableMetadataWriter.cs ├── PortableMetadata.Sample.ApplyAPatch ├── PortableMetadata.Sample.ApplyAPatch.csproj └── ServerAndClient.cs ├── PortableMetadata.Samples ├── NewtonsoftJson.cs ├── PortableMetadata.Samples.csproj ├── Program.cs ├── Sample_ApplyAPatch.cs ├── Sample_CustomOptions.cs ├── Sample_ExportOneMethod.cs ├── Sample_ImportOneMethod.cs ├── Sample_WholeAssemblyExportImport.cs └── SystemTextJson.cs ├── PortableMetadata.sln ├── PortableMetadata ├── Attributes.cs ├── PortableCommon.cs ├── PortableField.cs ├── PortableMetadata.cs ├── PortableMetadata.csproj ├── PortableMetadataEqualityComparer.cs ├── PortableMethod.cs └── PortableType.cs └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = crlf 7 | indent_size = 4 8 | indent_style = tab 9 | insert_final_newline = true 10 | tab_width = 4 11 | trim_trailing_whitespace = true 12 | 13 | # C# files 14 | [*.cs] 15 | 16 | #### .NET Coding Conventions #### 17 | 18 | # Organize usings 19 | dotnet_separate_import_directive_groups = false 20 | dotnet_sort_system_directives_first = true 21 | 22 | # this. and Me. preferences 23 | dotnet_style_qualification_for_event = false:suggestion 24 | dotnet_style_qualification_for_field = false:suggestion 25 | dotnet_style_qualification_for_method = false:suggestion 26 | dotnet_style_qualification_for_property = false:suggestion 27 | 28 | # Language keywords vs BCL types preferences 29 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 30 | dotnet_style_predefined_type_for_member_access = true:suggestion 31 | 32 | # Parentheses preferences 33 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion 34 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion 35 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion 36 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion 37 | 38 | # Modifier preferences 39 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 40 | 41 | # Expression-level preferences 42 | dotnet_style_coalesce_expression = true:suggestion 43 | dotnet_style_collection_initializer = true:suggestion 44 | dotnet_style_explicit_tuple_names = true:suggestion 45 | dotnet_style_namespace_match_folder = true:suggestion 46 | dotnet_style_null_propagation = true:suggestion 47 | dotnet_style_object_initializer = true:suggestion 48 | dotnet_style_operator_placement_when_wrapping = end_of_line 49 | dotnet_style_prefer_auto_properties = false:suggestion 50 | dotnet_style_prefer_compound_assignment = true:suggestion 51 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 52 | dotnet_style_prefer_conditional_expression_over_return = true:silent 53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 54 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 56 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 57 | dotnet_style_prefer_simplified_interpolation = true:suggestion 58 | 59 | # Field preferences 60 | dotnet_style_readonly_field = true:suggestion 61 | 62 | # Parameter preferences 63 | dotnet_code_quality_unused_parameters = all:suggestion 64 | 65 | # Suppression preferences 66 | dotnet_remove_unnecessary_suppression_exclusions = none 67 | 68 | # New line preferences 69 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 70 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 71 | 72 | #### C# Coding Conventions #### 73 | 74 | # var preferences 75 | csharp_style_var_elsewhere = true:suggestion 76 | csharp_style_var_for_built_in_types = false:silent 77 | csharp_style_var_when_type_is_apparent = true:suggestion 78 | 79 | # Expression-bodied members 80 | csharp_style_expression_bodied_accessors = true:suggestion 81 | csharp_style_expression_bodied_constructors = false:suggestion 82 | csharp_style_expression_bodied_indexers = true:suggestion 83 | csharp_style_expression_bodied_lambdas = true:suggestion 84 | csharp_style_expression_bodied_local_functions = false:suggestion 85 | csharp_style_expression_bodied_methods = false:suggestion 86 | csharp_style_expression_bodied_operators = false:suggestion 87 | csharp_style_expression_bodied_properties = true:suggestion 88 | 89 | # Pattern matching preferences 90 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 91 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 92 | csharp_style_prefer_not_pattern = true:suggestion 93 | csharp_style_prefer_pattern_matching = true:silent 94 | csharp_style_prefer_switch_expression = false:suggestion 95 | 96 | # Null-checking preferences 97 | csharp_style_conditional_delegate_call = true:suggestion 98 | 99 | # Modifier preferences 100 | csharp_prefer_static_local_function = true:suggestion 101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent 102 | 103 | # Code-block preferences 104 | csharp_prefer_braces = false:silent 105 | csharp_prefer_simple_using_statement = true:suggestion 106 | csharp_style_namespace_declarations = file_scoped:suggestion 107 | 108 | # Expression-level preferences 109 | csharp_prefer_simple_default_expression = true:suggestion 110 | csharp_style_deconstructed_variable_declaration = true:suggestion 111 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 112 | csharp_style_inlined_variable_declaration = true:suggestion 113 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 114 | csharp_style_prefer_index_operator = true:suggestion 115 | csharp_style_prefer_null_check_over_type_check = true:suggestion 116 | csharp_style_prefer_range_operator = true:suggestion 117 | csharp_style_throw_expression = true:suggestion 118 | csharp_style_unused_value_assignment_preference = unused_local_variable:silent 119 | csharp_style_unused_value_expression_statement_preference = unused_local_variable:silent 120 | 121 | # 'using' directive preferences 122 | csharp_using_directive_placement = outside_namespace:suggestion 123 | 124 | # New line preferences 125 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 126 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 127 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 128 | 129 | #### C# Formatting Rules #### 130 | 131 | # New line preferences 132 | csharp_new_line_before_catch = true 133 | csharp_new_line_before_else = true 134 | csharp_new_line_before_finally = true 135 | csharp_new_line_before_members_in_anonymous_types = true 136 | csharp_new_line_before_members_in_object_initializers = true 137 | csharp_new_line_before_open_brace = none 138 | csharp_new_line_between_query_expression_clauses = false 139 | 140 | # Indentation preferences 141 | csharp_indent_block_contents = true 142 | csharp_indent_braces = false 143 | csharp_indent_case_contents = true 144 | csharp_indent_case_contents_when_block = false 145 | csharp_indent_labels = one_less_than_current 146 | csharp_indent_switch_labels = false 147 | 148 | # Space preferences 149 | csharp_space_after_cast = false 150 | csharp_space_after_colon_in_inheritance_clause = true 151 | csharp_space_after_comma = true 152 | csharp_space_after_dot = false 153 | csharp_space_after_keywords_in_control_flow_statements = true 154 | csharp_space_after_semicolon_in_for_statement = true 155 | csharp_space_around_binary_operators = before_and_after 156 | csharp_space_around_declaration_statements = false 157 | csharp_space_before_colon_in_inheritance_clause = true 158 | csharp_space_before_comma = false 159 | csharp_space_before_dot = false 160 | csharp_space_before_open_square_brackets = false 161 | csharp_space_before_semicolon_in_for_statement = false 162 | csharp_space_between_empty_square_brackets = false 163 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 164 | csharp_space_between_method_call_name_and_opening_parenthesis = false 165 | csharp_space_between_method_call_parameter_list_parentheses = false 166 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 167 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 168 | csharp_space_between_method_declaration_parameter_list_parentheses = false 169 | csharp_space_between_parentheses = false 170 | csharp_space_between_square_brackets = false 171 | 172 | # Wrapping preferences 173 | csharp_preserve_single_line_blocks = true 174 | csharp_preserve_single_line_statements = true 175 | 176 | #### Naming styles #### 177 | 178 | # Naming rules 179 | 180 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 181 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 182 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 183 | 184 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 185 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 186 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 187 | 188 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 189 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 190 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 191 | 192 | # Symbol specifications 193 | 194 | dotnet_naming_symbols.interface.applicable_kinds = interface 195 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 196 | dotnet_naming_symbols.interface.required_modifiers = 197 | 198 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 199 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 200 | dotnet_naming_symbols.types.required_modifiers = 201 | 202 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 203 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 204 | dotnet_naming_symbols.non_field_members.required_modifiers = 205 | 206 | # Naming styles 207 | 208 | dotnet_naming_style.pascal_case.required_prefix = 209 | dotnet_naming_style.pascal_case.required_suffix = 210 | dotnet_naming_style.pascal_case.word_separator = 211 | dotnet_naming_style.pascal_case.capitalization = pascal_case 212 | 213 | dotnet_naming_style.begins_with_i.required_prefix = I 214 | dotnet_naming_style.begins_with_i.required_suffix = 215 | dotnet_naming_style.begins_with_i.word_separator = 216 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 217 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 wwh1004 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PortableMetadata.Benchmarks/PortableMetadata.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PortableMetadata.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | using System.Text.Encodings.Web; 4 | using System.Text.Json.Serialization; 5 | using System.Text.Json.Serialization.Metadata; 6 | using BenchmarkDotNet.Attributes; 7 | using BenchmarkDotNet.Running; 8 | using dnlib.DotNet; 9 | using MetadataSerialization; 10 | using MetadataSerialization.Dnlib; 11 | using Newtonsoft.Json; 12 | 13 | [MemoryDiagnoser] 14 | [GcServer(true), GcForce] 15 | public partial class Program { 16 | [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true)] 17 | [JsonSerializable(typeof(PortableMetadataFacade))] 18 | [JsonSerializable(typeof(bool)), JsonSerializable(typeof(char)), JsonSerializable(typeof(sbyte)), JsonSerializable(typeof(byte)), JsonSerializable(typeof(short)), 19 | JsonSerializable(typeof(ushort)), JsonSerializable(typeof(uint)), JsonSerializable(typeof(ulong)), JsonSerializable(typeof(float)), JsonSerializable(typeof(double))] 20 | partial class JsonSourceGenerationContext : JsonSerializerContext { } 21 | 22 | readonly ModuleDef module; 23 | readonly PortableMetadataOptions options; 24 | readonly PortableMetadata metadata; 25 | 26 | public Program() { 27 | module = ModuleDefMD.Load(File.ReadAllBytes("dnlib.dll")); 28 | options = /*PortableMetadataOptions.UseNamedToken | */PortableMetadataOptions.UseAssemblyFullName | PortableMetadataOptions.IncludeMethodBodies | PortableMetadataOptions.IncludeCustomAttributes; 29 | var reader = new PortableMetadataReader(module, options); 30 | metadata = reader.Metadata; 31 | foreach (var type in module.Types) 32 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren); 33 | } 34 | 35 | static void Main() { 36 | BenchmarkRunner.Run(); 37 | } 38 | 39 | [Benchmark] 40 | public void PortableMetadataReader() { 41 | var reader = new PortableMetadataReader(module, options); 42 | foreach (var type in module.Types) 43 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren); 44 | } 45 | 46 | [Benchmark] 47 | public void PortableMetadataWriter() { 48 | var module = new ModuleDefUser(); 49 | var writer = new PortableMetadataWriter(module, metadata); 50 | foreach (var type in metadata.Types.Values) { 51 | if (type is PortableTypeDef && type.Assembly is null) 52 | writer.AddType(type, PortableMetadataLevel.DefinitionWithChildren); 53 | } 54 | } 55 | 56 | [Benchmark] 57 | public void NewtonsoftJson() { 58 | var settings = new JsonSerializerSettings { 59 | NullValueHandling = NullValueHandling.Ignore 60 | }; 61 | var json = JsonConvert.SerializeObject(new PortableMetadataFacade(metadata), settings); 62 | LogJson(json); 63 | var obj = JsonConvert.DeserializeObject(json, settings)!.ToMetadata(); 64 | Compare(obj, metadata); 65 | } 66 | 67 | [Benchmark] 68 | public void NewtonsoftJsonWithConverts() { 69 | var settings = new JsonSerializerSettings { 70 | Converters = [new NJPortableTokenConverter(), new NJPortableComplexTypeConverter()], 71 | NullValueHandling = NullValueHandling.Ignore, 72 | ContractResolver = new NJPortableMetadataObjectPropertyRemover() 73 | }; 74 | var json = JsonConvert.SerializeObject(new PortableMetadataFacade(metadata), settings); 75 | LogJson(json); 76 | var obj = JsonConvert.DeserializeObject(json, settings)!.ToMetadata(); 77 | Compare(obj, metadata); 78 | } 79 | 80 | [Benchmark] 81 | public void SystemTextJson() { 82 | var options = new System.Text.Json.JsonSerializerOptions { 83 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 84 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 85 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals // Without converters, serializing NaN will throw an exception. 86 | }; 87 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 88 | LogJson(json); 89 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata(); 90 | Compare(obj, metadata); 91 | } 92 | 93 | [Benchmark] 94 | public void SystemTextJsonWithConverts() { 95 | var options = new System.Text.Json.JsonSerializerOptions { 96 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 97 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 98 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties) 99 | }; 100 | options.Converters.Add(new STJPortableTokenConverter()); 101 | options.Converters.Add(new STJPortableComplexTypeConverter()); 102 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 103 | LogJson(json); 104 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata(); 105 | Compare(obj, metadata); 106 | } 107 | 108 | [Benchmark] 109 | public void SystemTextJsonSourceGenerator() { 110 | var options = new System.Text.Json.JsonSerializerOptions { 111 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 112 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 113 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, // Without converters, serializing NaN will throw an exception. 114 | TypeInfoResolver = new JsonSourceGenerationContext() 115 | }; 116 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 117 | LogJson(json); 118 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata(); 119 | Compare(obj, metadata); 120 | } 121 | 122 | [Benchmark] 123 | public void SystemTextJsonSourceGeneratorWithConverts() { 124 | var options = new System.Text.Json.JsonSerializerOptions { 125 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 126 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 127 | TypeInfoResolver = new JsonSourceGenerationContext().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties) 128 | }; 129 | options.Converters.Add(new STJPortableTokenConverter()); 130 | options.Converters.Add(new STJPortableComplexTypeConverter()); 131 | var json = System.Text.Json.JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 132 | LogJson(json); 133 | var obj = System.Text.Json.JsonSerializer.Deserialize(json, options)!.ToMetadata(); 134 | Compare(obj, metadata); 135 | } 136 | 137 | static void LogJson(string json, [CallerMemberName] string name = "") { 138 | #if DEBUG 139 | Console.WriteLine(name + ": " + json.Length); 140 | File.WriteAllText(name + ".json", json); 141 | #endif 142 | } 143 | 144 | static void Compare(PortableMetadata x, PortableMetadata y) { 145 | Debug.Assert(x.Options == y.Options); 146 | Debug.Assert(x.Types.Count == y.Types.Count); 147 | var xt = x.Types.ToArray(); 148 | var yt = y.Types.ToArray(); 149 | for (int i = 0; i < xt.Length; i++) { 150 | Debug.Assert(xt[i].Key == yt[i].Key); 151 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xt[i].Value, yt[i].Value)); 152 | } 153 | Debug.Assert(x.Fields.Count == y.Fields.Count); 154 | var xf = x.Fields.ToArray(); 155 | var yf = y.Fields.ToArray(); 156 | for (int i = 0; i < xf.Length; i++) { 157 | Debug.Assert(xf[i].Key == yf[i].Key); 158 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xf[i].Value, yf[i].Value)); 159 | } 160 | Debug.Assert(x.Methods.Count == y.Methods.Count); 161 | var xm = x.Methods.ToArray(); 162 | var ym = y.Methods.ToArray(); 163 | for (int i = 0; i < xm.Length; i++) { 164 | Debug.Assert(xm[i].Key == ym[i].Key); 165 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xm[i].Value, ym[i].Value)); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /PortableMetadata.Dnlib/PortableMetadata.Dnlib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net35;net40;net45;netstandard2.0;net6.0;net8.0 4 | 12.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PortableMetadata.Dnlib/PortableMetadataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using dnlib.DotNet; 5 | using dnlib.DotNet.Emit; 6 | using dnlib.DotNet.Writer; 7 | 8 | namespace MetadataSerialization.Dnlib; 9 | 10 | /// 11 | /// The metadata reader that reads the metadata from the to the . 12 | /// 13 | public sealed class PortableMetadataReader : ICustomAttributeWriterHelper { 14 | readonly ModuleDef module; 15 | readonly PortableMetadata metadata; 16 | readonly PortableMetadataUpdater updater; 17 | 18 | /// 19 | /// Gets the associated with the . 20 | /// 21 | public ModuleDef Module => module; 22 | 23 | /// 24 | /// Gets the associated with the . 25 | /// 26 | public PortableMetadata Metadata => metadata; 27 | 28 | bool UseAssemblyFullName => (metadata.Options & PortableMetadataOptions.UseAssemblyFullName) != 0; 29 | 30 | bool IncludeMethodBodies => (metadata.Options & PortableMetadataOptions.IncludeMethodBodies) != 0; 31 | 32 | bool IncludeCustomAttributes => (metadata.Options & PortableMetadataOptions.IncludeCustomAttributes) != 0; 33 | 34 | bool KeepOldMaxStack => (metadata.Options & PortableMetadataOptions.KeepOldMaxStack) != 0; 35 | 36 | /// 37 | /// Constructor 38 | /// 39 | /// 40 | /// 41 | public PortableMetadataReader(ModuleDef module, PortableMetadataOptions options = PortableMetadata.DefaultOptions) { 42 | this.module = module; 43 | metadata = new PortableMetadata(options); 44 | updater = new PortableMetadataUpdater(metadata); 45 | } 46 | 47 | /// 48 | /// Add a type to . 49 | /// 50 | /// 51 | /// 52 | /// 53 | public PortableToken AddType(TypeDef type, PortableMetadataLevel level) { 54 | if (type is null) 55 | throw new ArgumentNullException(nameof(type)); 56 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.DefinitionWithChildren) 57 | throw new ArgumentOutOfRangeException(nameof(level)); 58 | if (type.Module != module) 59 | throw new ArgumentException("Type is not in the same module.", nameof(type)); 60 | 61 | // 1. Write the type reference 62 | var enclosingNames = type.DeclaringType is not null ? new List() : null; 63 | var enclosingType = type; 64 | while (enclosingType.DeclaringType is not null) { 65 | enclosingType = enclosingType.DeclaringType; 66 | enclosingNames!.Add(enclosingType.Name); 67 | } 68 | var typeRef = new PortableType(type.Name, enclosingType.Namespace, null, enclosingNames); 69 | var token = updater.Update(typeRef, PortableMetadataLevel.Reference, out var oldLevel); 70 | if (level <= oldLevel) 71 | return token; 72 | 73 | // 2. Write the type definition 74 | if (oldLevel < PortableMetadataLevel.Definition) { 75 | var baseType = type.BaseType is ITypeDefOrRef bt ? AddType(bt) : default(PortableComplexType?); 76 | var interfaces = AddInterfaces(type.Interfaces); 77 | var classLayout = AddClassLayout(type.ClassLayout); 78 | var genericParameters = AddGenericParameters(type.GenericParameters); 79 | var customAttributes = AddCustomAttributes(type.CustomAttributes); 80 | var typeDef = new PortableTypeDef(type.Name, enclosingType.Namespace, null, enclosingNames, 81 | (int)type.Attributes, baseType, interfaces, classLayout, genericParameters, customAttributes); 82 | var t = updater.Update(typeDef, PortableMetadataLevel.Definition, out _); 83 | Debug.Assert(t == token); 84 | } 85 | if (level <= PortableMetadataLevel.Definition) 86 | return token; 87 | 88 | // 3. Write the children 89 | if (oldLevel < PortableMetadataLevel.DefinitionWithChildren) { 90 | var typeDef = (PortableTypeDef)metadata.Types[token]; 91 | typeDef.NestedTypes = AddTypes(type.NestedTypes, level); 92 | typeDef.Fields = AddFields(type.Fields, PortableMetadataLevel.Definition); 93 | typeDef.Methods = AddMethods(type.Methods, PortableMetadataLevel.Definition); 94 | typeDef.Properties = AddProperties(type.Properties); 95 | typeDef.Events = AddEvents(type.Events); 96 | var t = updater.Update(typeDef, PortableMetadataLevel.DefinitionWithChildren, out _); 97 | Debug.Assert(t == token); 98 | } 99 | if (level <= PortableMetadataLevel.DefinitionWithChildren) 100 | return token; 101 | 102 | throw new InvalidOperationException(); 103 | } 104 | 105 | /// 106 | /// Add a type to . 107 | /// 108 | /// 109 | /// 110 | /// 111 | public PortableComplexType AddType(TypeSpec type) { 112 | if (type is null) 113 | throw new ArgumentNullException(nameof(type)); 114 | 115 | return AddTypeSig(type.TypeSig); 116 | } 117 | 118 | /// 119 | /// Add a field to . 120 | /// 121 | /// 122 | /// 123 | /// 124 | public PortableToken AddField(FieldDef field, PortableMetadataLevel level) { 125 | if (field is null) 126 | throw new ArgumentNullException(nameof(field)); 127 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.Definition) 128 | throw new ArgumentOutOfRangeException(nameof(level)); 129 | if (field.Module != module) 130 | throw new ArgumentException("Field is not in the same module.", nameof(field)); 131 | 132 | // 1. Write the field reference 133 | var type = AddType(field.DeclaringType); 134 | var signature = AddCallingConventionSig(field.Signature); 135 | var fieldRef = new PortableField(field.Name, type, signature); 136 | var token = updater.Update(fieldRef, PortableMetadataLevel.Reference, out var oldLevel); 137 | if (level <= oldLevel) 138 | return token; 139 | 140 | // 2. Write the field definition 141 | if (oldLevel < PortableMetadataLevel.Definition) { 142 | var constant = AddConstant(field.Constant); 143 | var customAttributes = AddCustomAttributes(field.CustomAttributes); 144 | var fieldDef = new PortableFieldDef(field.Name, type, signature, (ushort)field.Attributes, field.InitialValue, 145 | constant, customAttributes); 146 | var t = updater.Update(fieldDef, PortableMetadataLevel.Definition, out _); 147 | Debug.Assert(t == token); 148 | } 149 | if (level <= PortableMetadataLevel.Definition) 150 | return token; 151 | 152 | throw new InvalidOperationException(); 153 | } 154 | 155 | /// 156 | /// Add a method to . 157 | /// 158 | /// 159 | /// 160 | /// 161 | public PortableToken AddMethod(MethodDef method, PortableMetadataLevel level) { 162 | if (method is null) 163 | throw new ArgumentNullException(nameof(method)); 164 | if (level < PortableMetadataLevel.Reference || level > PortableMetadataLevel.Definition) 165 | throw new ArgumentOutOfRangeException(nameof(level)); 166 | if (method.Module != module) 167 | throw new ArgumentException("Method is not in the same module.", nameof(method)); 168 | 169 | // 1. Write the method reference 170 | var type = AddType(method.DeclaringType); 171 | var signature = AddCallingConventionSig(method.Signature); 172 | var methodRef = new PortableMethod(method.Name, type, signature); 173 | var token = updater.Update(methodRef, PortableMetadataLevel.Reference, out var oldLevel); 174 | if (level <= oldLevel) 175 | return token; 176 | 177 | // 2. Write the method definition 178 | if (oldLevel < PortableMetadataLevel.Definition) { 179 | var parameters = AddParameters(method.ParamDefs); 180 | var body = IncludeMethodBodies ? AddMethodBody(method.Body) : null; 181 | var overrides = AddMethodOverrides(method.Overrides); 182 | var implMap = AddImplMap(method.ImplMap); 183 | var genericParameters = AddGenericParameters(method.GenericParameters); 184 | var customAttributes = AddCustomAttributes(method.CustomAttributes); 185 | var methodDef = new PortableMethodDef(method.Name, type, signature, (ushort)method.Attributes, (ushort)method.ImplAttributes, parameters, body, 186 | overrides, implMap, genericParameters, customAttributes); 187 | var t = updater.Update(methodDef, PortableMetadataLevel.Definition, out _); 188 | Debug.Assert(t == token); 189 | } 190 | if (level <= PortableMetadataLevel.Definition) 191 | return token; 192 | 193 | throw new InvalidOperationException(); 194 | } 195 | 196 | #region Wrappers 197 | PortableToken AddType(TypeRef type) { 198 | if (type is null) 199 | throw new ArgumentNullException(nameof(type)); 200 | if (type.ResolutionScope is ModuleRef) 201 | throw new NotSupportedException("Doesn't support the reference to the types of another module."); 202 | 203 | var assembly = UseAssemblyFullName ? type.DefinitionAssembly.FullName : (string)type.DefinitionAssembly.Name; 204 | var enclosingNames = type.DeclaringType is not null ? new List() : null; 205 | var enclosingType = type; 206 | while (enclosingType.DeclaringType is not null) { 207 | enclosingType = enclosingType.DeclaringType; 208 | enclosingNames!.Add(enclosingType.Name); 209 | } 210 | var typeRef = new PortableType(type.Name, enclosingType.Namespace, assembly, enclosingNames); 211 | return updater.Update(typeRef, PortableMetadataLevel.Reference, out _); 212 | } 213 | 214 | PortableComplexType AddType(ITypeDefOrRef type, bool allowTypeSpec = true) { 215 | if (type is null) 216 | throw new ArgumentNullException(nameof(type)); 217 | 218 | if (type is TypeDef td) 219 | return PortableComplexType.CreateToken(AddType(td, PortableMetadataLevel.Reference)); 220 | else if (type is TypeRef tr) 221 | return PortableComplexType.CreateToken(AddType(tr)); 222 | else if (allowTypeSpec && type is TypeSpec ts) 223 | return AddType(ts); 224 | else 225 | throw new NotSupportedException(); 226 | } 227 | 228 | PortableToken AddField(MemberRef field) { 229 | if (field is null) 230 | throw new ArgumentNullException(nameof(field)); 231 | if (!field.IsFieldRef) 232 | throw new ArgumentException("MemberRef is not a field reference", nameof(field)); 233 | if (field.Class is ModuleRef) 234 | throw new NotSupportedException("Doesn't support the reference to the members of another module's ."); 235 | 236 | var type = AddType(field.DeclaringType); 237 | var signature = AddCallingConventionSig(field.Signature); 238 | var fieldRef = new PortableField(field.Name, type, signature); 239 | return updater.Update(fieldRef, PortableMetadataLevel.Reference, out _); 240 | } 241 | 242 | PortableToken AddField(IField field) { 243 | if (field is null) 244 | throw new ArgumentNullException(nameof(field)); 245 | 246 | if (field is FieldDef fd) 247 | return AddField(fd, PortableMetadataLevel.Reference); 248 | else if (field is MemberRef fr) 249 | return AddField(fr); 250 | else 251 | throw new NotSupportedException(); 252 | } 253 | 254 | PortableToken AddMethod(MemberRef method) { 255 | if (method is null) 256 | throw new ArgumentNullException(nameof(method)); 257 | if (!method.IsMethodRef) 258 | throw new ArgumentException("MemberRef is not a method reference", nameof(method)); 259 | if (method.Class is MethodDef) 260 | throw new NotSupportedException("Doesn't support the varargs method reference."); 261 | if (method.Class is ModuleRef) 262 | throw new NotSupportedException("Doesn't support the reference to the members of another module's ."); 263 | 264 | var type = AddType(method.DeclaringType); 265 | var signature = AddCallingConventionSig(method.Signature); 266 | var methodRef = new PortableMethod(method.Name, type, signature); 267 | return updater.Update(methodRef, PortableMetadataLevel.Reference, out _); 268 | } 269 | 270 | PortableToken AddMethod(IMethodDefOrRef method) { 271 | if (method is null) 272 | throw new ArgumentNullException(nameof(method)); 273 | 274 | if (method is MethodDef md) 275 | return AddMethod(md, PortableMetadataLevel.Reference); 276 | else if (method is MemberRef mr) 277 | return AddMethod(mr); 278 | else 279 | throw new NotSupportedException(); 280 | } 281 | 282 | /// 283 | /// Add the types to . 284 | /// 285 | /// 286 | /// 287 | /// 288 | public List AddTypes(IEnumerable types, PortableMetadataLevel level) { 289 | if (types is null) 290 | throw new ArgumentNullException(nameof(types)); 291 | 292 | if (Enumerable2.NewList(types, out List list)) 293 | return list; 294 | foreach (var t in types) { 295 | var type = AddType(t, level); 296 | list.Add(type); 297 | } 298 | return list; 299 | } 300 | 301 | /// 302 | /// Add the fields to . 303 | /// 304 | /// 305 | /// 306 | /// 307 | public List AddFields(IEnumerable fields, PortableMetadataLevel level) { 308 | if (fields is null) 309 | throw new ArgumentNullException(nameof(fields)); 310 | 311 | if (Enumerable2.NewList(fields, out List list)) 312 | return list; 313 | foreach (var f in fields) { 314 | var field = AddField(f, level); 315 | list.Add(field); 316 | } 317 | return list; 318 | } 319 | 320 | /// 321 | /// Add the methods to . 322 | /// 323 | /// 324 | /// 325 | /// 326 | public List AddMethods(IEnumerable methods, PortableMetadataLevel level) { 327 | if (methods is null) 328 | throw new ArgumentNullException(nameof(methods)); 329 | 330 | if (Enumerable2.NewList(methods, out List list)) 331 | return list; 332 | foreach (var m in methods) { 333 | var method = AddMethod(m, level); 334 | list.Add(method); 335 | } 336 | return list; 337 | } 338 | #endregion 339 | 340 | #region Private 341 | List? AddCustomAttributes(CustomAttributeCollection customAttributes) { 342 | if (!IncludeCustomAttributes || customAttributes.Count == 0) 343 | return null; 344 | var list = new List(customAttributes.Count); 345 | foreach (var ca in customAttributes) { 346 | var ctor = AddMethod((IMethodDefOrRef)ca.Constructor); 347 | var data = CustomAttributeWriter.Write(this, ca); 348 | list.Add(new PortableCustomAttribute(ctor, data)); 349 | } 350 | return list; 351 | } 352 | 353 | List? AddGenericParameters(IList genericParameters) { 354 | if (genericParameters.Count == 0) 355 | return null; 356 | var list = new List(genericParameters.Count); 357 | foreach (var gp in genericParameters) { 358 | var gpcs = gp.GenericParamConstraints; 359 | List? constraints = null; 360 | if (gpcs.Count != 0) { 361 | constraints = new List(gpcs.Count); 362 | foreach (var gpc in gpcs) { 363 | var type = AddType(gpc.Constraint); 364 | constraints!.Add(type); 365 | } 366 | } 367 | list.Add(new PortableGenericParameter(gp.Name, (ushort)gp.Flags, gp.Number, constraints)); 368 | } 369 | return list; 370 | } 371 | 372 | static PortableConstant? AddConstant(Constant? constant) { 373 | if (constant is null) 374 | return null; 375 | return new PortableConstant((int)constant.Type, constant.Value); 376 | } 377 | 378 | List? AddInterfaces(IList interfaces) { 379 | if (interfaces.Count == 0) 380 | return null; 381 | var list = new List(interfaces.Count); 382 | foreach (var i in interfaces) { 383 | var type = AddType(i.Interface); 384 | list.Add(type); 385 | } 386 | return list; 387 | } 388 | 389 | static PortableClassLayout? AddClassLayout(ClassLayout? classLayout) { 390 | if (classLayout is null) 391 | return null; 392 | return new PortableClassLayout(classLayout.PackingSize, (int)classLayout.ClassSize); 393 | } 394 | 395 | List AddParameters(IList parameters) { 396 | var list = new List(parameters.Count); 397 | foreach (var p in parameters) { 398 | var constant = AddConstant(p.Constant); 399 | var customAttributes = AddCustomAttributes(p.CustomAttributes); 400 | list.Add(new PortableParameter(p.Name, p.Sequence, (ushort)p.Attributes, constant, customAttributes)); 401 | } 402 | return list; 403 | } 404 | 405 | List? AddMethodOverrides(IList overrides) { 406 | if (overrides.Count == 0) 407 | return null; 408 | var list = new List(overrides.Count); 409 | foreach (var o in overrides) { 410 | var method = AddMethod(o.MethodDeclaration); 411 | list.Add(method); 412 | } 413 | return list; 414 | } 415 | 416 | static PortableImplMap? AddImplMap(ImplMap? implMap) { 417 | if (implMap is null) 418 | return null; 419 | return new PortableImplMap(implMap.Name, implMap.Module.Name, (ushort)implMap.Attributes); 420 | } 421 | 422 | List AddProperties(IList properties) { 423 | var list = new List(properties.Count); 424 | foreach (var p in properties) { 425 | var signature = AddCallingConventionSig(p.PropertySig); 426 | var getMethod = p.GetMethod is MethodDef get ? AddMethod(get) : default(PortableToken?); 427 | var setMethod = p.SetMethod is MethodDef set ? AddMethod(set) : default(PortableToken?); 428 | var customAttributes = AddCustomAttributes(p.CustomAttributes); 429 | list.Add(new PortableProperty(p.Name, signature, (int)p.Attributes, getMethod, setMethod, customAttributes)); 430 | } 431 | return list; 432 | } 433 | 434 | List AddEvents(IList events) { 435 | var list = new List(events.Count); 436 | foreach (var e in events) { 437 | var type = AddType(e.EventType); 438 | var addMethod = e.AddMethod is MethodDef add ? AddMethod(add) : default(PortableToken?); 439 | var removeMethod = e.RemoveMethod is MethodDef remove ? AddMethod(remove) : default(PortableToken?); 440 | var invokeMethod = e.InvokeMethod is MethodDef invoke ? AddMethod(invoke) : default(PortableToken?); 441 | var customAttributes = AddCustomAttributes(e.CustomAttributes); 442 | list.Add(new PortableEvent(e.Name, type, (int)e.Attributes, addMethod, removeMethod, invokeMethod, customAttributes)); 443 | } 444 | return list; 445 | } 446 | 447 | bool IFullNameFactoryHelper.MustUseAssemblyName(IType type) { 448 | return FullNameFactory.MustUseAssemblyName(module, type, true); 449 | } 450 | 451 | void IWriterError.Error(string message) { 452 | // TODO: implement this method. 453 | } 454 | #endregion 455 | 456 | #region Method Body 457 | PortableMethodBody? AddMethodBody(CilBody? body) { 458 | if (body is null) 459 | return null; 460 | 461 | var indexes = new Dictionary(body.Instructions.Count); 462 | int index = 0; 463 | foreach (var instr in body.Instructions) 464 | indexes.Add(instr, index++); 465 | 466 | var instructions = new List(body.Instructions.Count); 467 | foreach (var instr in body.Instructions) { 468 | object? operand; 469 | switch (instr.OpCode.OperandType) { 470 | case OperandType.InlineBrTarget: 471 | case OperandType.ShortInlineBrTarget: 472 | operand = indexes[(Instruction)instr.Operand]; 473 | break; 474 | 475 | case OperandType.InlineField: 476 | case OperandType.InlineMethod: 477 | case OperandType.InlineSig: 478 | case OperandType.InlineTok: 479 | case OperandType.InlineType: 480 | operand = AddToken(instr.Operand); 481 | break; 482 | 483 | case OperandType.InlineI: 484 | case OperandType.InlineI8: 485 | case OperandType.InlineNone: 486 | case OperandType.InlinePhi: 487 | case OperandType.InlineR: 488 | case OperandType.InlineString: 489 | case OperandType.ShortInlineR: 490 | operand = instr.Operand; 491 | break; 492 | 493 | case OperandType.InlineSwitch: { 494 | var targets = (IList)instr.Operand; 495 | var newTargets = new int[targets.Count]; 496 | operand = newTargets; 497 | for (int i = 0; i < targets.Count; i++) 498 | newTargets[i] = indexes[targets[i]]; 499 | break; 500 | } 501 | 502 | case OperandType.InlineVar: 503 | case OperandType.ShortInlineVar: 504 | operand = ((IVariable)instr.Operand).Index; 505 | break; 506 | 507 | case OperandType.ShortInlineI: 508 | operand = instr.Operand is sbyte sb ? sb : (int)(byte)instr.Operand; 509 | break; 510 | 511 | default: 512 | throw new NotSupportedException(); 513 | } 514 | 515 | instructions.Add(new PortableInstruction(instr.OpCode.Name, operand)); 516 | } 517 | 518 | var exceptionHandlers = new List(body.ExceptionHandlers.Count); 519 | foreach (var eh in body.ExceptionHandlers) { 520 | int tryStart = indexes[eh.TryStart]; 521 | int tryEnd = eh.TryEnd is not null ? indexes[eh.TryEnd] : -1; 522 | int filterStart = eh.FilterStart is not null ? indexes[eh.FilterStart] : -1; 523 | int handlerStart = indexes[eh.HandlerStart]; 524 | int handlerEnd = eh.HandlerEnd is not null ? indexes[eh.HandlerEnd] : -1; 525 | var catchType = eh.CatchType is not null ? AddType(eh.CatchType) : default(PortableComplexType?); 526 | int handlerType = (int)eh.HandlerType; 527 | exceptionHandlers.Add(new PortableExceptionHandler(tryStart, tryEnd, filterStart, handlerStart, handlerEnd, catchType, handlerType)); 528 | } 529 | 530 | var variables = new List(body.Variables.Count); 531 | foreach (var v in body.Variables) 532 | variables.Add(AddTypeSig(v.Type)); 533 | 534 | int maxStack = KeepOldMaxStack ? body.MaxStack : (int)MaxStackCalculator.GetMaxStack(body.Instructions, body.ExceptionHandlers); 535 | return new PortableMethodBody(instructions, exceptionHandlers, variables, maxStack, body.InitLocals); 536 | } 537 | 538 | PortableComplexType AddToken(object o) { 539 | if (o is CallingConventionSig sig) { 540 | Debug.Assert(sig is MethodSig || sig is LocalSig); 541 | return AddCallingConventionSig(sig); 542 | } 543 | 544 | PortableComplexTypeKind kind; 545 | PortableToken? token = null; 546 | PortableComplexType? type = null; 547 | switch (o) { 548 | case TypeDef td: 549 | kind = PortableComplexTypeKind.InlineType; 550 | token = AddType(td, PortableMetadataLevel.Reference); 551 | break; 552 | case TypeRef tr: 553 | kind = PortableComplexTypeKind.InlineType; 554 | token = AddType(tr); 555 | break; 556 | case TypeSpec ts: 557 | kind = PortableComplexTypeKind.InlineType; 558 | type = AddType(ts); 559 | break; 560 | 561 | case FieldDef fd: 562 | kind = PortableComplexTypeKind.InlineField; 563 | token = AddField(fd, PortableMetadataLevel.Reference); 564 | break; 565 | case MemberRef { IsFieldRef: true } fr: 566 | kind = PortableComplexTypeKind.InlineField; 567 | token = AddField(fr); 568 | break; 569 | 570 | case MethodDef md: 571 | kind = PortableComplexTypeKind.InlineMethod; 572 | token = AddMethod(md, PortableMetadataLevel.Reference); 573 | break; 574 | case MemberRef { IsMethodRef: true } mr: 575 | kind = PortableComplexTypeKind.InlineMethod; 576 | token = AddMethod(mr); 577 | break; 578 | case MethodSpec ms: 579 | kind = PortableComplexTypeKind.InlineMethod; 580 | token = AddMethod(ms.Method); 581 | type = AddCallingConventionSig(ms.Instantiation); 582 | break; 583 | 584 | default: 585 | throw new NotSupportedException(); 586 | } 587 | 588 | if (token is PortableToken token2) { 589 | if (type is PortableComplexType type2) 590 | type = PortableComplexType.CreateMethodSpec(PortableComplexType.CreateToken(token2), type2); 591 | else 592 | type = PortableComplexType.CreateToken(token2); 593 | } 594 | return PortableComplexType.CreateInlineTokenOperand(kind, type!.Value); 595 | } 596 | #endregion 597 | 598 | #region Signature 599 | PortableComplexType AddTypeSig(TypeSig typeSig) { 600 | byte elementType = (byte)typeSig.ElementType; 601 | switch ((ElementType)elementType) { 602 | case ElementType.End: 603 | case ElementType.Void: 604 | case ElementType.Boolean: 605 | case ElementType.Char: 606 | case ElementType.I1: 607 | case ElementType.U1: 608 | case ElementType.I2: 609 | case ElementType.U2: 610 | case ElementType.I4: 611 | case ElementType.U4: 612 | case ElementType.I8: 613 | case ElementType.U8: 614 | case ElementType.R4: 615 | case ElementType.R8: 616 | case ElementType.String: 617 | case ElementType.TypedByRef: 618 | case ElementType.I: 619 | case ElementType.U: 620 | case ElementType.R: 621 | case ElementType.Object: 622 | case ElementType.Sentinel: 623 | // et 624 | return PortableComplexType.CreateTypeSig(elementType, null); 625 | 626 | case ElementType.Ptr: 627 | case ElementType.ByRef: 628 | case ElementType.FnPtr: 629 | case ElementType.SZArray: 630 | case ElementType.Pinned: 631 | // et(next) 632 | return PortableComplexType.CreateTypeSig(elementType, [AddTypeSig(typeSig.Next)]); 633 | 634 | case ElementType.ValueType: 635 | case ElementType.Class: 636 | // et(next) 637 | return PortableComplexType.CreateTypeSig(elementType, [AddType(((ClassOrValueTypeSig)typeSig).TypeDefOrRef, false)]); 638 | 639 | case ElementType.Var: 640 | case ElementType.MVar: 641 | // et(index) 642 | return PortableComplexType.CreateTypeSig(elementType, [PortableComplexType.CreateInt32((int)((GenericSig)typeSig).Number)]); 643 | 644 | case ElementType.Array: { 645 | // et(next, rank, numSizes, .. sizes, numLowerBounds, .. lowerBounds) 646 | var arraySig = (ArraySig)typeSig; 647 | var sizes = arraySig.Sizes; 648 | var lowerBounds = arraySig.LowerBounds; 649 | var arguments = new List(4 + sizes.Count + lowerBounds.Count) { 650 | AddTypeSig(arraySig.Next), 651 | PortableComplexType.CreateInt32((int)arraySig.Rank), 652 | PortableComplexType.CreateInt32(sizes.Count) 653 | }; 654 | foreach (uint size in sizes) 655 | arguments.Add(PortableComplexType.CreateInt32((int)size)); 656 | arguments.Add(PortableComplexType.CreateInt32(lowerBounds.Count)); 657 | foreach (int lowerBound in lowerBounds) 658 | arguments.Add(PortableComplexType.CreateInt32(lowerBound)); 659 | return PortableComplexType.CreateTypeSig(elementType, arguments); 660 | } 661 | 662 | case ElementType.GenericInst: { 663 | // et(next, num, .. args) 664 | var instSig = (GenericInstSig)typeSig; 665 | var arguments = new List(2 + instSig.GenericArguments.Count) { 666 | AddTypeSig(instSig.GenericType), 667 | PortableComplexType.CreateInt32(instSig.GenericArguments.Count) 668 | }; 669 | foreach (var arg in instSig.GenericArguments) 670 | arguments.Add(AddTypeSig(arg)); 671 | return PortableComplexType.CreateTypeSig(elementType, arguments); 672 | } 673 | 674 | case ElementType.ValueArray: 675 | // et(next, size) 676 | return PortableComplexType.CreateTypeSig(elementType, [AddTypeSig(((ValueArraySig)typeSig).Next), PortableComplexType.CreateInt32((int)((ValueArraySig)typeSig).Size)]); 677 | 678 | case ElementType.CModReqd: 679 | case ElementType.CModOpt: 680 | // et(modifier, next) 681 | return PortableComplexType.CreateTypeSig(elementType, [AddType(((ModifierSig)typeSig).Modifier), AddTypeSig(((ModifierSig)typeSig).Next)]); 682 | 683 | case ElementType.Module: 684 | // et(index, next) 685 | return PortableComplexType.CreateTypeSig(elementType, [PortableComplexType.CreateInt32((int)((ModuleSig)typeSig).Index), AddTypeSig(((ModuleSig)typeSig).Next)]); 686 | 687 | default: 688 | throw new InvalidOperationException(); 689 | } 690 | } 691 | 692 | PortableComplexType AddCallingConventionSig(CallingConventionSig callingConventionSig) { 693 | byte callingConvention = (byte)(callingConventionSig.GetCallingConvention() & CallingConvention.Mask); 694 | int flags = (byte)(callingConventionSig.GetCallingConvention() & ~CallingConvention.Mask); 695 | List arguments = [PortableComplexType.CreateInt32(flags)]; 696 | switch ((CallingConvention)callingConvention) { 697 | case CallingConvention.Default: 698 | case CallingConvention.C: 699 | case CallingConvention.StdCall: 700 | case CallingConvention.ThisCall: 701 | case CallingConvention.FastCall: 702 | case CallingConvention.VarArg: 703 | case CallingConvention.Property: 704 | case CallingConvention.Unmanaged: 705 | case CallingConvention.NativeVarArg: { 706 | // cc([numGPs], numParams, retType, .. params) 707 | var methodSig = (MethodBaseSig)callingConventionSig; 708 | if (methodSig.Generic) 709 | arguments.Add(PortableComplexType.CreateInt32((int)methodSig.GenParamCount)); 710 | arguments.Add(PortableComplexType.CreateInt32(methodSig.Params.Count)); 711 | arguments.Add(AddTypeSig(methodSig.RetType)); 712 | foreach (var param in methodSig.Params) 713 | arguments.Add(AddTypeSig(param)); 714 | if (methodSig.ParamsAfterSentinel is not null && methodSig.ParamsAfterSentinel.Count > 0) { 715 | arguments.Add(PortableComplexType.CreateTypeSig((byte)ElementType.Sentinel, null)); 716 | foreach (var param in methodSig.ParamsAfterSentinel) 717 | arguments.Add(AddTypeSig(param)); 718 | } 719 | break; 720 | } 721 | 722 | case CallingConvention.Field: 723 | // cc(fieldType) 724 | arguments.Add(AddTypeSig(((FieldSig)callingConventionSig).Type)); 725 | break; 726 | 727 | case CallingConvention.LocalSig: { 728 | // cc(numLocals, .. locals) 729 | var locals = ((LocalSig)callingConventionSig).Locals; 730 | arguments.Add(PortableComplexType.CreateInt32(locals.Count)); 731 | foreach (var local in locals) 732 | arguments.Add(AddTypeSig(local)); 733 | break; 734 | } 735 | 736 | case CallingConvention.GenericInst: { 737 | // cc(numArgs, .. args) 738 | var instMethodSig = (GenericInstMethodSig)callingConventionSig; 739 | arguments.Add(PortableComplexType.CreateInt32(instMethodSig.GenericArguments.Count)); 740 | foreach (var arg in instMethodSig.GenericArguments) 741 | arguments.Add(AddTypeSig(arg)); 742 | break; 743 | } 744 | 745 | default: 746 | throw new InvalidOperationException(); 747 | } 748 | return PortableComplexType.CreateCallingConventionSig(callingConvention, arguments); 749 | } 750 | #endregion 751 | } 752 | 753 | static class Enumerable2 { 754 | public static bool NewList(IEnumerable a, out List b) { 755 | if (a is IList l) { 756 | if (l.Count == 0) { 757 | b = []; 758 | return true; 759 | } 760 | b = new List(l.Count); 761 | } 762 | else 763 | b = []; 764 | return false; 765 | } 766 | } 767 | -------------------------------------------------------------------------------- /PortableMetadata.Sample.ApplyAPatch/PortableMetadata.Sample.ApplyAPatch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | enable 5 | enable 6 | 7 | 8 | -------------------------------------------------------------------------------- /PortableMetadata.Sample.ApplyAPatch/ServerAndClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | using System.Net; 3 | using System.Text; 4 | 5 | public sealed class MyServer { 6 | readonly TcpListener listener = new(IPAddress.Loopback, 0); 7 | 8 | public int Start() { 9 | listener.Start(); 10 | listener.AcceptTcpClientAsync().ContinueWith(async t => { 11 | using var stream = t.Result.GetStream(); 12 | while (true) { 13 | var buffer = new byte[4]; 14 | await stream.ReadExactlyAsync(buffer); 15 | int length = BitConverter.ToInt32(buffer, 0); 16 | buffer = new byte[length]; 17 | await stream.ReadExactlyAsync(buffer); 18 | var s = Encoding.UTF8.GetString(buffer); 19 | Console.WriteLine($"Data from client: {s}"); 20 | } 21 | }, TaskContinuationOptions.OnlyOnRanToCompletion); 22 | return ((IPEndPoint)listener.LocalEndpoint).Port; 23 | } 24 | } 25 | 26 | public sealed class MyClient { 27 | public string GetUserName() { 28 | return Environment.UserName; 29 | } 30 | 31 | public void Start(int port) { 32 | var client = new TcpClient(); 33 | client.ConnectAsync(IPAddress.Loopback, port).ContinueWith(async _ => { 34 | using var stream = client.GetStream(); 35 | for (int i = 0; i < 5; i++) { 36 | var name = GetUserName(); 37 | int length = Encoding.UTF8.GetByteCount(name); 38 | var buffer = BitConverter.GetBytes(length); 39 | await stream.WriteAsync(buffer); 40 | buffer = Encoding.UTF8.GetBytes(name); 41 | await stream.WriteAsync(buffer); 42 | await Task.Delay(1000); 43 | } 44 | }, TaskContinuationOptions.OnlyOnRanToCompletion); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/NewtonsoftJson.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | using MetadataSerialization; 5 | 6 | sealed class NJPortableTokenConverter : JsonConverter { 7 | public override PortableToken? ReadJson(JsonReader reader, Type objectType, PortableToken? existingValue, bool hasExistingValue, JsonSerializer serializer) { 8 | if (reader.Value is long index) 9 | return (int)index; 10 | else if (reader.Value is string name) 11 | return name; 12 | else 13 | return null; 14 | } 15 | 16 | public override void WriteJson(JsonWriter writer, PortableToken? value, JsonSerializer serializer) { 17 | if (value is not PortableToken token) 18 | writer.WriteNull(); 19 | else if (token.Name is string name) 20 | writer.WriteValue(name); 21 | else 22 | writer.WriteValue(token.Index); 23 | } 24 | } 25 | 26 | sealed class NJPortableComplexTypeConverter : JsonConverter { 27 | public override PortableComplexType? ReadJson(JsonReader reader, Type objectType, PortableComplexType? existingValue, bool hasExistingValue, JsonSerializer serializer) { 28 | return reader.Value is string type ? PortableComplexType.Parse(type) : null; 29 | } 30 | 31 | public override void WriteJson(JsonWriter writer, PortableComplexType? value, JsonSerializer serializer) { 32 | if (value is PortableComplexType type) 33 | writer.WriteValue(type.ToString()); 34 | else 35 | writer.WriteNull(); 36 | } 37 | } 38 | 39 | sealed class NJPortableMetadataObjectPropertyRemover : DefaultContractResolver { 40 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { 41 | var property = base.CreateProperty(member, memberSerialization); 42 | if (property.PropertyType == typeof(object)) 43 | property.Ignored = true; 44 | return property; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/PortableMetadata.Samples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 26 | 27 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Program.cs: -------------------------------------------------------------------------------- 1 | Sample_ExportOneMethod.Run(); 2 | Sample_ImportOneMethod.Run(); 3 | Sample_CustomOptions.Run(); 4 | Sample_ApplyAPatch.Run(); 5 | Sample_WholeAssemblyExportImport.Run(); 6 | Console.ReadKey(true); 7 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Sample_ApplyAPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using System.Text.Json.Serialization.Metadata; 6 | using dnlib.DotNet; 7 | using dnlib.DotNet.Emit; 8 | using MetadataSerialization; 9 | using MetadataSerialization.Dnlib; 10 | 11 | static class MyClientPatch { 12 | public record struct MyUserName(string Value); 13 | 14 | public static string MyGetUserName() { 15 | var data = ReadFile("myuser.json"); 16 | MyUserName obj; 17 | try { 18 | obj = JsonSerializer.Deserialize(data); 19 | } 20 | catch { 21 | return "??"; 22 | } 23 | return obj.Value; 24 | } 25 | 26 | static string ReadFile(string path) { 27 | while (true) { 28 | try { 29 | if (!File.Exists(path)) 30 | goto next; 31 | return File.ReadAllText(path); 32 | } 33 | catch { 34 | goto next; 35 | } 36 | next: 37 | Thread.Sleep(100); 38 | } 39 | } 40 | } 41 | 42 | static class Sample_ApplyAPatch { 43 | public static void Run() { 44 | // 1. Get the patch info of MyClient class 45 | var patch = GetPatch(); 46 | var patchMetadata = JsonSerializer.Deserialize(patch, CreateJsonSerializerOptions())!.ToMetadata(); 47 | 48 | // 2. Apply the patch 49 | using var module = ModuleDefMD.Load(File.ReadAllBytes("PortableMetadata.Sample.ApplyAPatch.dll")); 50 | var writer = new PortableMetadataWriter(module, patchMetadata); 51 | var myCientPatch = writer.AddType(patchMetadata.Types.Values.First(t => t.Name == "MyClientPatch"), PortableMetadataLevel.DefinitionWithChildren); 52 | var getUserName = module.FindNormalThrow("MyClient").FindMethod("GetUserName"); 53 | getUserName.FreeMethodBody(); 54 | var body = getUserName.Body = new CilBody(); 55 | body.Instructions.Add(OpCodes.Call.ToInstruction(myCientPatch.FindMethod("MyGetUserName"))); 56 | body.Instructions.Add(OpCodes.Ret.ToInstruction()); 57 | module.Name = module.Assembly.Name = Guid.NewGuid().ToString(); 58 | var path = Path.GetFullPath("PortableMetadata.Sample.ApplyAPatch.Patched.dll"); 59 | module.Write(path); 60 | 61 | // 3. Test the patched assembly 62 | File.Delete("myuser.json"); 63 | var assembly = Assembly.LoadFrom(path); 64 | dynamic server = Activator.CreateInstance(assembly.GetType("MyServer")!)!; 65 | int port = server.Start(); 66 | dynamic client = Activator.CreateInstance(assembly.GetType("MyClient")!)!; 67 | client.Start(port); 68 | var names = new List { "Alice", "Bob", "Charlie", "David", "Eve" }; 69 | for (int i = 0; i < 5; i++) { 70 | WriteFile("myuser.json", JsonSerializer.Serialize(new(names[i]))); 71 | Thread.Sleep(1000); 72 | } 73 | Thread.Sleep(500); 74 | } 75 | 76 | /* 77 | Output: 78 | Data from client: Alice 79 | Data from client: Bob 80 | Data from client: Charlie 81 | Data from client: David 82 | Data from client: Eve 83 | */ 84 | 85 | static string GetPatch() { 86 | // 1. Load the module using dnlib 87 | using var module = ModuleDefMD.Load(typeof(MyClientPatch).Module); 88 | 89 | // 2. Create the portable metadata 90 | var reader = new PortableMetadataReader(module, PortableMetadata.DefaultOptions | PortableMetadataOptions.UseNamedToken); 91 | reader.AddType(module.FindNormalThrow("MyClientPatch"), PortableMetadataLevel.DefinitionWithChildren); 92 | var metadata = reader.Metadata; 93 | 94 | // 4. Export the patch 95 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), CreateJsonSerializerOptions()); 96 | var path = Path.GetFullPath("patch.json"); 97 | File.WriteAllText(path, json); 98 | try { 99 | Process.Start("notepad", path)?.Dispose(); 100 | } 101 | catch { 102 | } 103 | return json; 104 | } 105 | 106 | static JsonSerializerOptions CreateJsonSerializerOptions() { 107 | var options = new JsonSerializerOptions { 108 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 109 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 110 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 111 | WriteIndented = true 112 | }; 113 | options.Converters.Add(new STJPortableTokenConverter()); 114 | options.Converters.Add(new STJPortableComplexTypeConverter()); 115 | return options; 116 | } 117 | 118 | static void WriteFile(string path, string content) { 119 | while (true) { 120 | try { 121 | File.WriteAllText(path, content); 122 | return; 123 | } 124 | catch { 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Sample_CustomOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using System.Text.Json.Serialization.Metadata; 5 | using dnlib.DotNet; 6 | using MetadataSerialization; 7 | using MetadataSerialization.Dnlib; 8 | 9 | static class Sample_CustomOptions { 10 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] 11 | sealed class MyAttribute : Attribute { } 12 | 13 | [My] 14 | public static void DemoMethod([CallerMemberName] string arg = "") { 15 | Console.WriteLine($"Hello World: {arg}"); 16 | } 17 | 18 | public static void Run() { 19 | // 1. Load the module using dnlib 20 | using var module = ModuleDefMD.Load(typeof(Sample_CustomOptions).Module); 21 | 22 | // 2. Create the portable metadata with custom options (use named token and exclude custom attributes) 23 | var reader = new PortableMetadataReader(module, PortableMetadataOptions.UseNamedToken | PortableMetadataOptions.IncludeMethodBodies); 24 | reader.AddMethod(module.FindNormalThrow("Sample_CustomOptions").FindMethod("DemoMethod"), PortableMetadataLevel.Definition); 25 | var metadata = reader.Metadata; 26 | 27 | // 3. Export the metadata 28 | var options = new JsonSerializerOptions { 29 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 30 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 31 | Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 32 | WriteIndented = true 33 | }; 34 | options.Converters.Add(new STJPortableTokenConverter()); 35 | options.Converters.Add(new STJPortableComplexTypeConverter()); 36 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 37 | Console.WriteLine(json); 38 | } 39 | 40 | /* 41 | Output: 42 | { 43 | "Options": 5, 44 | "Types": { 45 | "References": { 46 | "Sample_CustomOptions": { 47 | "Name": "Sample_CustomOptions", 48 | "Namespace": "" 49 | }, 50 | "String": { 51 | "Name": "String", 52 | "Namespace": "System", 53 | "Assembly": "System.Runtime" 54 | }, 55 | "Console": { 56 | "Name": "Console", 57 | "Namespace": "System", 58 | "Assembly": "System.Console" 59 | } 60 | }, 61 | "Definitions": {}, 62 | "Orders": [] 63 | }, 64 | "Fields": { 65 | "References": {}, 66 | "Definitions": {}, 67 | "Orders": [] 68 | }, 69 | "Methods": { 70 | "References": { 71 | "String::Concat": { 72 | "Name": "Concat", 73 | "Type": "'String'", 74 | "Signature": "Default(Int32(0),Int32(2),String,String,String)" 75 | }, 76 | "Console::WriteLine": { 77 | "Name": "WriteLine", 78 | "Type": "'Console'", 79 | "Signature": "Default(Int32(0),Int32(1),Void,String)" 80 | } 81 | }, 82 | "Definitions": { 83 | "Sample_CustomOptions::DemoMethod": { 84 | "Attributes": 150, 85 | "ImplAttributes": 0, 86 | "Parameters": [ 87 | { 88 | "Name": "arg", 89 | "Sequence": 1, 90 | "Attributes": 4112, 91 | "Constant": { 92 | "Type": 14, 93 | "StringValue": "" 94 | } 95 | } 96 | ], 97 | "Body": { 98 | "Instructions": [ 99 | { 100 | "OpCode": "nop" 101 | }, 102 | { 103 | "OpCode": "ldstr", 104 | "StringValue": "Hello World: " 105 | }, 106 | { 107 | "OpCode": "ldarg.0" 108 | }, 109 | { 110 | "OpCode": "call", 111 | "TypeValue": "InlineMethod('String::Concat')" 112 | }, 113 | { 114 | "OpCode": "call", 115 | "TypeValue": "InlineMethod('Console::WriteLine')" 116 | }, 117 | { 118 | "OpCode": "nop" 119 | }, 120 | { 121 | "OpCode": "ret" 122 | } 123 | ], 124 | "ExceptionHandlers": [], 125 | "Variables": [] 126 | }, 127 | "Name": "DemoMethod", 128 | "Type": "'Sample_CustomOptions'", 129 | "Signature": "Default(Int32(0),Int32(1),Void,String)" 130 | } 131 | }, 132 | "Orders": [ 133 | 0, 134 | 1 135 | ] 136 | } 137 | } 138 | */ 139 | } 140 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Sample_ExportOneMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | using System.Text.Json.Serialization.Metadata; 4 | using dnlib.DotNet; 5 | using MetadataSerialization; 6 | using MetadataSerialization.Dnlib; 7 | 8 | static class Sample_ExportOneMethod { 9 | public static void DemoMethod() { 10 | Console.WriteLine("Hello World!"); 11 | } 12 | 13 | public static void Run() { 14 | // 1. Load the module using dnlib 15 | using var module = ModuleDefMD.Load(typeof(Sample_ExportOneMethod).Module); 16 | 17 | // 2. Create the portable metadata 18 | var reader = new PortableMetadataReader(module); 19 | reader.AddMethod(module.FindNormalThrow("Sample_ExportOneMethod").FindMethod("DemoMethod"), PortableMetadataLevel.Definition); 20 | var metadata = reader.Metadata; 21 | 22 | // 3. Export the metadata (without any converters) 23 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); 24 | Console.WriteLine(json); 25 | 26 | // 4. Export the metadata (with converters) 27 | var options = new JsonSerializerOptions { 28 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 29 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 30 | WriteIndented = true 31 | }; 32 | options.Converters.Add(new STJPortableTokenConverter()); 33 | options.Converters.Add(new STJPortableComplexTypeConverter()); 34 | json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 35 | Console.WriteLine(json); 36 | } 37 | 38 | /* 39 | Output: 40 | {"Options":14,"Types":{"References":{"0":{"Name":"Sample_ExportOneMethod","Namespace":""},"1":{"Name":"Console","Namespace":"System","Assembly":"System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"}},"Definitions":{},"Orders":[]},"Fields":{"References":{},"Definitions":{},"Orders":[]},"Methods":{"References":{"1":{"Name":"WriteLine","Type":{"Kind":0,"Token":{"Index":1},"Type":0},"Signature":{"Kind":2,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":3,"Token":{"Index":1},"Type":0},{"Kind":1,"Token":{"Index":0},"Type":1},{"Kind":1,"Token":{"Index":0},"Type":14}]}}},"Definitions":{"0":{"Attributes":150,"ImplAttributes":0,"Parameters":[],"Body":{"Instructions":[{"OpCode":"nop"},{"OpCode":"ldstr","Operand":"Hello World!","StringValue":"Hello World!"},{"OpCode":"call","Operand":{"Kind":7,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":0,"Token":{"Index":1},"Type":0}]},"TypeValue":{"Kind":7,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":0,"Token":{"Index":1},"Type":0}]}},{"OpCode":"nop"},{"OpCode":"ret"}],"ExceptionHandlers":[],"Variables":[]},"Name":"DemoMethod","Type":{"Kind":0,"Token":{"Index":0},"Type":0},"Signature":{"Kind":2,"Token":{"Index":0},"Type":0,"Arguments":[{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":3,"Token":{"Index":0},"Type":0},{"Kind":1,"Token":{"Index":0},"Type":1}]}}},"Orders":[]}} 41 | { 42 | "Options": 14, 43 | "Types": { 44 | "References": { 45 | "0": { 46 | "Name": "Sample_ExportOneMethod", 47 | "Namespace": "" 48 | }, 49 | "1": { 50 | "Name": "Console", 51 | "Namespace": "System", 52 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 53 | } 54 | }, 55 | "Definitions": {}, 56 | "Orders": [] 57 | }, 58 | "Fields": { 59 | "References": {}, 60 | "Definitions": {}, 61 | "Orders": [] 62 | }, 63 | "Methods": { 64 | "References": { 65 | "1": { 66 | "Name": "WriteLine", 67 | "Type": "1", 68 | "Signature": "Default(Int32(0),Int32(1),Void,String)" 69 | } 70 | }, 71 | "Definitions": { 72 | "0": { 73 | "Attributes": 150, 74 | "ImplAttributes": 0, 75 | "Parameters": [], 76 | "Body": { 77 | "Instructions": [ 78 | { 79 | "OpCode": "nop" 80 | }, 81 | { 82 | "OpCode": "ldstr", 83 | "StringValue": "Hello World!" 84 | }, 85 | { 86 | "OpCode": "call", 87 | "TypeValue": "InlineMethod(1)" 88 | }, 89 | { 90 | "OpCode": "nop" 91 | }, 92 | { 93 | "OpCode": "ret" 94 | } 95 | ], 96 | "ExceptionHandlers": [], 97 | "Variables": [] 98 | }, 99 | "Name": "DemoMethod", 100 | "Type": "0", 101 | "Signature": "Default(Int32(0),Int32(0),Void)" 102 | } 103 | }, 104 | "Orders": [] 105 | } 106 | } 107 | */ 108 | } 109 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Sample_ImportOneMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using System.Text.Json.Serialization.Metadata; 6 | using dnlib.DotNet; 7 | using MetadataSerialization; 8 | using MetadataSerialization.Dnlib; 9 | 10 | static class Sample_ImportOneMethod { 11 | public static void DemoMethod() { 12 | //var list = new List { 13 | // "Hello", 14 | // "World" 15 | //}; 16 | //foreach (var item in list) 17 | // Console.WriteLine(item); 18 | throw new NotImplementedException(); 19 | } 20 | 21 | public static void Run() { 22 | // 1. Load the module using dnlib 23 | using var module = ModuleDefMD.Load(typeof(Sample_ImportOneMethod).Module); 24 | 25 | // 2. Deserialize the metadata 26 | var options = new JsonSerializerOptions { 27 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 28 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 29 | WriteIndented = true 30 | }; 31 | options.Converters.Add(new STJPortableTokenConverter()); 32 | options.Converters.Add(new STJPortableComplexTypeConverter()); 33 | var metadata = JsonSerializer.Deserialize(DemoMethodJson, options)!.ToMetadata(); 34 | var demoMethod = metadata.Methods[0]; 35 | Debug.Assert(demoMethod is PortableMethodDef); 36 | 37 | // 3. Import the method 38 | var writer = new PortableMetadataWriter(module, metadata); 39 | writer.AddMethod(demoMethod, PortableMetadataLevel.Definition); 40 | var path = Path.GetFullPath("Sample_ImportOneMethod.dll"); 41 | module.Assembly.Name = module.Name = Guid.NewGuid().ToString(); 42 | module.Write(path); 43 | Assembly.LoadFrom(path).GetType("Sample_ImportOneMethod")!.GetMethod("DemoMethod")!.Invoke(null, null); 44 | } 45 | 46 | const string DemoMethodJson = 47 | """ 48 | { 49 | "Options": 14, 50 | "Types": { 51 | "References": { 52 | "0": { 53 | "Name": "Sample_ImportOneMethod", 54 | "Namespace": "" 55 | }, 56 | "1": { 57 | "Name": "List\u00601", 58 | "Namespace": "System.Collections.Generic", 59 | "Assembly": "System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 60 | }, 61 | "2": { 62 | "Name": "Enumerator", 63 | "Namespace": "System.Collections.Generic", 64 | "Assembly": "System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", 65 | "EnclosingNames": [ 66 | "List\u00601" 67 | ] 68 | }, 69 | "3": { 70 | "Name": "Console", 71 | "Namespace": "System", 72 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 73 | }, 74 | "4": { 75 | "Name": "IDisposable", 76 | "Namespace": "System", 77 | "Assembly": "System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 78 | } 79 | }, 80 | "Definitions": {}, 81 | "Orders": [] 82 | }, 83 | "Fields": { 84 | "References": {}, 85 | "Definitions": {}, 86 | "Orders": [] 87 | }, 88 | "Methods": { 89 | "References": { 90 | "1": { 91 | "Name": ".ctor", 92 | "Type": "GenericInst(Class(1),Int32(1),String)", 93 | "Signature": "Default(Int32(32),Int32(0),Void)" 94 | }, 95 | "2": { 96 | "Name": "Add", 97 | "Type": "GenericInst(Class(1),Int32(1),String)", 98 | "Signature": "Default(Int32(32),Int32(1),Void,Var(Int32(0)))" 99 | }, 100 | "3": { 101 | "Name": "GetEnumerator", 102 | "Type": "GenericInst(Class(1),Int32(1),String)", 103 | "Signature": "Default(Int32(32),Int32(0),GenericInst(ValueType(2),Int32(1),Var(Int32(0))))" 104 | }, 105 | "4": { 106 | "Name": "get_Current", 107 | "Type": "GenericInst(ValueType(2),Int32(1),String)", 108 | "Signature": "Default(Int32(32),Int32(0),Var(Int32(0)))" 109 | }, 110 | "5": { 111 | "Name": "WriteLine", 112 | "Type": "3", 113 | "Signature": "Default(Int32(0),Int32(1),Void,String)" 114 | }, 115 | "6": { 116 | "Name": "MoveNext", 117 | "Type": "GenericInst(ValueType(2),Int32(1),String)", 118 | "Signature": "Default(Int32(32),Int32(0),Boolean)" 119 | }, 120 | "7": { 121 | "Name": "Dispose", 122 | "Type": "4", 123 | "Signature": "Default(Int32(32),Int32(0),Void)" 124 | } 125 | }, 126 | "Definitions": { 127 | "0": { 128 | "Attributes": 150, 129 | "ImplAttributes": 0, 130 | "Parameters": [], 131 | "Body": { 132 | "Instructions": [ 133 | { 134 | "OpCode": "nop" 135 | }, 136 | { 137 | "OpCode": "newobj", 138 | "TypeValue": "InlineMethod(1)" 139 | }, 140 | { 141 | "OpCode": "dup" 142 | }, 143 | { 144 | "OpCode": "ldstr", 145 | "StringValue": "Hello" 146 | }, 147 | { 148 | "OpCode": "callvirt", 149 | "TypeValue": "InlineMethod(2)" 150 | }, 151 | { 152 | "OpCode": "nop" 153 | }, 154 | { 155 | "OpCode": "dup" 156 | }, 157 | { 158 | "OpCode": "ldstr", 159 | "StringValue": "World" 160 | }, 161 | { 162 | "OpCode": "callvirt", 163 | "TypeValue": "InlineMethod(2)" 164 | }, 165 | { 166 | "OpCode": "nop" 167 | }, 168 | { 169 | "OpCode": "stloc.0" 170 | }, 171 | { 172 | "OpCode": "nop" 173 | }, 174 | { 175 | "OpCode": "ldloc.0" 176 | }, 177 | { 178 | "OpCode": "callvirt", 179 | "TypeValue": "InlineMethod(3)" 180 | }, 181 | { 182 | "OpCode": "stloc.1" 183 | }, 184 | { 185 | "OpCode": "br.s", 186 | "PrimitiveValue": 22 187 | }, 188 | { 189 | "OpCode": "ldloca.s", 190 | "PrimitiveValue": 1 191 | }, 192 | { 193 | "OpCode": "call", 194 | "TypeValue": "InlineMethod(4)" 195 | }, 196 | { 197 | "OpCode": "stloc.2" 198 | }, 199 | { 200 | "OpCode": "ldloc.2" 201 | }, 202 | { 203 | "OpCode": "call", 204 | "TypeValue": "InlineMethod(5)" 205 | }, 206 | { 207 | "OpCode": "nop" 208 | }, 209 | { 210 | "OpCode": "ldloca.s", 211 | "PrimitiveValue": 1 212 | }, 213 | { 214 | "OpCode": "call", 215 | "TypeValue": "InlineMethod(6)" 216 | }, 217 | { 218 | "OpCode": "brtrue.s", 219 | "PrimitiveValue": 16 220 | }, 221 | { 222 | "OpCode": "leave.s", 223 | "PrimitiveValue": 31 224 | }, 225 | { 226 | "OpCode": "ldloca.s", 227 | "PrimitiveValue": 1 228 | }, 229 | { 230 | "OpCode": "constrained.", 231 | "TypeValue": "InlineType(GenericInst(ValueType(2),Int32(1),String))" 232 | }, 233 | { 234 | "OpCode": "callvirt", 235 | "TypeValue": "InlineMethod(7)" 236 | }, 237 | { 238 | "OpCode": "nop" 239 | }, 240 | { 241 | "OpCode": "endfinally" 242 | }, 243 | { 244 | "OpCode": "ret" 245 | } 246 | ], 247 | "ExceptionHandlers": [ 248 | { 249 | "TryStart": 15, 250 | "TryEnd": 26, 251 | "FilterStart": -1, 252 | "HandlerStart": 26, 253 | "HandlerEnd": 31, 254 | "HandlerType": 2 255 | } 256 | ], 257 | "Variables": [ 258 | "GenericInst(Class(1),Int32(1),String)", 259 | "GenericInst(ValueType(2),Int32(1),String)", 260 | "String" 261 | ] 262 | }, 263 | "Name": "DemoMethod", 264 | "Type": "0", 265 | "Signature": "Default(Int32(0),Int32(0),Void)" 266 | } 267 | }, 268 | "Orders": [] 269 | } 270 | } 271 | """; 272 | } 273 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/Sample_WholeAssemblyExportImport.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using System.Text.Json.Serialization.Metadata; 6 | using dnlib.DotNet; 7 | using dnlib.DotNet.MD; 8 | using MetadataSerialization; 9 | using MetadataSerialization.Dnlib; 10 | 11 | static class Sample_WholeAssemblyExportImport { 12 | public static void DemoMethod() { 13 | var assembly = Assembly.GetExecutingAssembly(); 14 | Console.WriteLine(assembly.FullName); 15 | Console.WriteLine(assembly.Location); 16 | } 17 | 18 | public static void Run() { 19 | // Test dnlib.dll 20 | Run(typeof(ModuleDef).Module, "dnlib2"); 21 | 22 | // Test current assembly 23 | var path = Run(typeof(Sample_WholeAssemblyExportImport).Module, "Sample_WholeAssemblyExportImport"); 24 | Assembly.LoadFrom(path).GetType("Sample_WholeAssemblyExportImport")!.GetMethod("DemoMethod")!.Invoke(null, null); 25 | } 26 | 27 | /* 28 | Output: 29 | 431e8643-0ed9-49b9-97af-c020c0dacd62, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null 30 | X:\bin\Debug\net8.0\Sample_WholeAssemblyExportImport.dll 31 | */ 32 | 33 | static string Run(Module reflModule, string name) { 34 | // 1. Load the module using dnlib 35 | using var module = ModuleDefMD.Load(reflModule); 36 | 37 | // 2. Import all the typedefs 38 | var reader = new PortableMetadataReader(module); 39 | foreach (var type in module.Types) 40 | reader.AddType(type, PortableMetadataLevel.DefinitionWithChildren); 41 | var metadata = reader.Metadata; 42 | 43 | // 3. Serialize the metadata 44 | var options = new JsonSerializerOptions { 45 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 46 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties) 47 | }; 48 | options.Converters.Add(new STJPortableTokenConverter()); 49 | options.Converters.Add(new STJPortableComplexTypeConverter()); 50 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 51 | File.WriteAllText($"{name}.json", json); 52 | 53 | // 4. Deserialize the metadata 54 | var metadata2 = JsonSerializer.Deserialize(json, options)!.ToMetadata(); 55 | Compare(metadata, metadata2); 56 | 57 | // 5. Create the new module 58 | var module2 = new ModuleDefUser(Guid.NewGuid().ToString()) { RuntimeVersion = MDHeaderRuntimeVersion.MS_CLR_40 }; 59 | new AssemblyDefUser(module2.Name).Modules.Add(module2); 60 | var writer = new PortableMetadataWriter(module2, metadata2); 61 | foreach (var type in metadata2.Types.Values.OfType()) 62 | writer.AddType(type, PortableMetadataLevel.DefinitionWithChildren); 63 | var path = Path.GetFullPath($"{name}.dll"); 64 | module2.Write(path); 65 | return path; 66 | } 67 | 68 | static void Compare(PortableMetadata x, PortableMetadata y) { 69 | Debug.Assert(x.Options == y.Options); 70 | Debug.Assert(x.Types.Count == y.Types.Count); 71 | var xt = x.Types.ToArray(); 72 | var yt = y.Types.ToArray(); 73 | for (int i = 0; i < xt.Length; i++) { 74 | Debug.Assert(xt[i].Key == yt[i].Key); 75 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xt[i].Value, yt[i].Value)); 76 | } 77 | Debug.Assert(x.Fields.Count == y.Fields.Count); 78 | var xf = x.Fields.ToArray(); 79 | var yf = y.Fields.ToArray(); 80 | for (int i = 0; i < xf.Length; i++) { 81 | Debug.Assert(xf[i].Key == yf[i].Key); 82 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xf[i].Value, yf[i].Value)); 83 | } 84 | Debug.Assert(x.Methods.Count == y.Methods.Count); 85 | var xm = x.Methods.ToArray(); 86 | var ym = y.Methods.ToArray(); 87 | for (int i = 0; i < xm.Length; i++) { 88 | Debug.Assert(xm[i].Key == ym[i].Key); 89 | Debug.Assert(PortableMetadataEqualityComparer.FullComparer.Equals(xm[i].Value, ym[i].Value)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /PortableMetadata.Samples/SystemTextJson.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | using System.Text.Json.Serialization.Metadata; 4 | using MetadataSerialization; 5 | 6 | sealed class STJPortableTokenConverter : JsonConverter { 7 | public override PortableToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { 8 | if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out int index)) 9 | return (PortableToken)index; 10 | else if (reader.TokenType == JsonTokenType.String) 11 | return (PortableToken)reader.GetString()!; 12 | else 13 | throw new InvalidDataException(); 14 | } 15 | 16 | public override void Write(Utf8JsonWriter writer, PortableToken value, JsonSerializerOptions options) { 17 | if (value.Name is string name) 18 | writer.WriteStringValue(name); 19 | else 20 | writer.WriteNumberValue(value.Index); 21 | } 22 | } 23 | 24 | sealed class STJPortableComplexTypeConverter : JsonConverter { 25 | public override PortableComplexType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { 26 | return reader.GetString() is string s ? PortableComplexType.Parse(s) : throw new InvalidDataException(); 27 | } 28 | 29 | public override void Write(Utf8JsonWriter writer, PortableComplexType value, JsonSerializerOptions options) { 30 | writer.WriteStringValue(value.ToString()); 31 | } 32 | } 33 | 34 | static class STJPortableMetadataObjectPropertyRemover { 35 | public static void RemoveObjectProperties(JsonTypeInfo typeInfo) { 36 | if (typeInfo.Kind != JsonTypeInfoKind.Object) 37 | return; 38 | // Don't use Properties.RemoteAt, which breaks the source generator mode. 39 | foreach (var property in typeInfo.Properties) { 40 | if (property.PropertyType == typeof(object)) { 41 | property.Get = null; 42 | property.Set = null; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PortableMetadata.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata", "PortableMetadata\PortableMetadata.csproj", "{449DDA11-5956-48FB-A220-63E219E3C43A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Benchmarks", "PortableMetadata.Benchmarks\PortableMetadata.Benchmarks.csproj", "{90494317-C1E5-461B-8A20-F223AC8FBA7A}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Samples", "PortableMetadata.Samples\PortableMetadata.Samples.csproj", "{6BDFFE09-162E-4A54-A8A3-C790465EFB8E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortableMetadata.Sample.ApplyAPatch", "PortableMetadata.Sample.ApplyAPatch\PortableMetadata.Sample.ApplyAPatch.csproj", "{C70261EF-3F5B-4F02-A69C-5129716A06F5}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortableMetadata.Dnlib", "PortableMetadata.Dnlib\PortableMetadata.Dnlib.csproj", "{0A4B416A-7272-488E-9034-A1B7D06F4FC7}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {449DDA11-5956-48FB-A220-63E219E3C43A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {90494317-C1E5-461B-8A20-F223AC8FBA7A}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {6BDFFE09-162E-4A54-A8A3-C790465EFB8E}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {C70261EF-3F5B-4F02-A69C-5129716A06F5}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {0A4B416A-7272-488E-9034-A1B7D06F4FC7}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {9D20BEA2-0224-4200-8A1B-F19A380389D0} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /PortableMetadata/Attributes.cs: -------------------------------------------------------------------------------- 1 | #if !NET 2 | using System.ComponentModel; 3 | 4 | namespace System.Diagnostics.CodeAnalysis { 5 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 6 | sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute { 7 | public bool ReturnValue { get; } = returnValue; 8 | } 9 | } 10 | 11 | namespace System.Runtime.CompilerServices { 12 | [EditorBrowsable(EditorBrowsableState.Never)] 13 | static class IsExternalInit { } 14 | } 15 | #endif 16 | 17 | #if NETFRAMEWORK && !NET35_OR_GREATER 18 | namespace System { 19 | delegate TResult Func(T arg); 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /PortableMetadata/PortableCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text; 6 | using ElementType2 = MetadataSerialization.PortableComplexTypeFormatter.ElementType; 7 | 8 | namespace MetadataSerialization; 9 | 10 | /// 11 | /// Represents a metadata token. 12 | /// 13 | public readonly struct PortableToken : IEquatable, IComparable { 14 | /// 15 | /// Invalid characters for a token name. 16 | /// 17 | public static readonly char[] InvalidNameChars = ['(', ')', ',', '@']; 18 | 19 | /// 20 | /// Token index. 21 | /// 22 | public int Index { get; init; } 23 | 24 | /// 25 | /// Token name. 26 | /// 27 | /// Only valid when is enabled. 28 | public string? Name { get; init; } 29 | /// 30 | /// Initializes a new instance of the class with the specified index. 31 | /// 32 | /// The index of the token. 33 | public PortableToken(int index) { 34 | Index = index; 35 | Name = null; 36 | } 37 | 38 | /// 39 | /// Initializes a new instance of the class with the specified name. 40 | /// 41 | /// The name of the token. 42 | /// Thrown when the name is null or empty. 43 | public PortableToken(string name) { 44 | if (string.IsNullOrEmpty(name)) 45 | throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name)); 46 | 47 | Debug.Assert(name.IndexOfAny(InvalidNameChars) == -1, "Contains invalid characters."); 48 | Index = 0; 49 | Name = name; 50 | } 51 | 52 | /// 53 | /// Determines whether the specified is equal to the current . 54 | /// 55 | /// The to compare with the current . 56 | /// true if the specified is equal to the current ; otherwise, false. 57 | public bool Equals(PortableToken other) { 58 | return Index == other.Index && Name == other.Name; 59 | } 60 | 61 | /// 62 | /// Determines whether the specified object is equal to the current . 63 | /// 64 | /// The object to compare with the current . 65 | /// true if the specified object is equal to the current ; otherwise, false. 66 | public override bool Equals(object? obj) { 67 | return obj is PortableToken other && Equals(other); 68 | } 69 | 70 | /// 71 | /// Compares the current with another . 72 | /// 73 | /// The to compare with the current . 74 | /// A value that indicates the relative order of the objects being compared. 75 | public int CompareTo(PortableToken other) { 76 | if (Name is not null) { 77 | // Named tokens are always lesser than unnamed tokens 78 | if (other.Name is null) 79 | return -1; 80 | return Name.CompareTo(other.Name); 81 | } 82 | else { 83 | // Unnamed tokens 84 | if (other.Name is not null) 85 | return 1; 86 | return Index.CompareTo(other.Index); 87 | } 88 | } 89 | 90 | /// 91 | /// Returns the hash code for the current . 92 | /// 93 | /// A 32-bit signed integer hash code. 94 | public override int GetHashCode() { 95 | return Name is not null ? Name.GetHashCode() : Index; 96 | } 97 | 98 | /// 99 | /// Returns a string that represents the current . 100 | /// 101 | /// A string that represents the current . 102 | public override string ToString() { 103 | return Name is not null ? Name : Index.ToString(); 104 | } 105 | 106 | #region Operators 107 | /// 108 | public static implicit operator PortableToken(int index) { return new PortableToken(index); } 109 | /// 110 | public static implicit operator PortableToken(string name) { return new PortableToken(name); } 111 | /// 112 | public static explicit operator int(PortableToken token) { return token.Index; } 113 | /// 114 | public static explicit operator string(PortableToken token) { return token.Name ?? throw new InvalidCastException("Token is not named."); } 115 | /// 116 | public static bool operator ==(PortableToken x, PortableToken y) { return x.Equals(y); } 117 | /// 118 | public static bool operator !=(PortableToken x, PortableToken y) { return !x.Equals(y); } 119 | /// 120 | public static bool operator <(PortableToken x, PortableToken y) { return x.CompareTo(y) < 0; } 121 | /// 122 | public static bool operator >(PortableToken x, PortableToken y) { return x.CompareTo(y) > 0; } 123 | /// 124 | public static bool operator <=(PortableToken x, PortableToken y) { return x.CompareTo(y) <= 0; } 125 | /// 126 | public static bool operator >=(PortableToken x, PortableToken y) { return x.CompareTo(y) >= 0; } 127 | #endregion 128 | } 129 | 130 | /// 131 | /// Represents the kind of . 132 | /// 133 | public enum PortableComplexTypeKind : byte { 134 | /// 135 | /// A . 136 | /// 137 | Token, 138 | 139 | /// 140 | /// A type signature. 141 | /// 142 | TypeSig, 143 | 144 | /// 145 | /// A calling convention signature. 146 | /// 147 | CallingConventionSig, 148 | 149 | /// 150 | /// An integer value. 151 | /// 152 | Int32, 153 | 154 | /// 155 | /// A method specification. (An instantiated generic method) 156 | /// 157 | MethodSpec, 158 | 159 | /// 160 | /// Inline type/token operand wrapper. 161 | /// 162 | InlineType, 163 | 164 | /// 165 | /// Inline field/token operand wrapper. 166 | /// 167 | InlineField, 168 | 169 | /// 170 | /// Inline method/token operand wrapper. 171 | /// 172 | InlineMethod 173 | } 174 | 175 | /// 176 | /// Represents a portable complex type. 177 | /// 178 | /// 179 | /// TypeDef/TypeRef/TypeSpec/TypeDefOrRef 180 | /// FieldDef/MemberRef 181 | /// MethodDef/MemberRef/MethodSpec/MethodDefOrRef 182 | /// TypeSig/CallingConventionSig 183 | /// 184 | public struct PortableComplexType { 185 | /// 186 | /// Gets or sets the kind of . 187 | /// 188 | public PortableComplexTypeKind Kind { get; set; } 189 | 190 | /// 191 | /// Gets or sets the token. 192 | /// 193 | /// See 194 | public PortableToken Token { get; set; } 195 | 196 | /// 197 | /// Gets or sets the element type or calling convention. 198 | /// 199 | /// See and 200 | public byte Type { get; set; } 201 | 202 | /// 203 | /// Gets or sets the arguments. 204 | /// 205 | public IList? Arguments { get; set; } 206 | 207 | /// 208 | /// Gets the integer value stored in the . 209 | /// 210 | /// 211 | /// See 212 | public readonly int GetInt32() { 213 | return Kind == PortableComplexTypeKind.Int32 ? Token.Index : throw new InvalidOperationException(); 214 | } 215 | 216 | private PortableComplexType(PortableComplexTypeKind kind, PortableToken token, byte type, IList? arguments) { 217 | Kind = kind; 218 | Token = token; 219 | Type = type; 220 | Arguments = arguments; 221 | } 222 | 223 | /// 224 | /// Creates a complex type with the specified token. 225 | /// 226 | /// The token of the complex type. 227 | /// A complex type with the specified token. 228 | public static PortableComplexType CreateToken(PortableToken token) { 229 | return new PortableComplexType(PortableComplexTypeKind.Token, token, 0, null); 230 | } 231 | 232 | /// 233 | /// Creates a complex type with the specified element type and arguments. 234 | /// 235 | /// The element type of the complex type. 236 | /// The arguments of the complex type. 237 | /// A complex type with the specified element type and arguments. 238 | public static PortableComplexType CreateTypeSig(byte elementType, IList? arguments) { 239 | return new PortableComplexType(PortableComplexTypeKind.TypeSig, default, elementType, arguments); 240 | } 241 | 242 | /// 243 | /// Creates a complex type with the specified calling convention and arguments. 244 | /// 245 | /// The calling convention of the complex type. 246 | /// The arguments of the complex type. 247 | /// A complex type with the specified calling convention and arguments. 248 | public static PortableComplexType CreateCallingConventionSig(byte callingConvention, IList? arguments) { 249 | return new PortableComplexType(PortableComplexTypeKind.CallingConventionSig, default, callingConvention, arguments); 250 | } 251 | 252 | /// 253 | /// Creates a complex type with the specified integer value. 254 | /// 255 | /// The integer value of the complex type. 256 | /// A complex type with the specified integer value. 257 | public static PortableComplexType CreateInt32(int value) { 258 | return new PortableComplexType(PortableComplexTypeKind.Int32, value, 0, null); 259 | } 260 | 261 | /// 262 | /// Creates a complex type with the specified method and instantiation. 263 | /// 264 | /// The method of the complex type. 265 | /// The instantiation of the complex type. 266 | /// A complex type with the specified method and instantiation. 267 | public static PortableComplexType CreateMethodSpec(PortableComplexType method, PortableComplexType instantiation) { 268 | return new PortableComplexType(PortableComplexTypeKind.MethodSpec, default, 0, [method, instantiation]); 269 | } 270 | 271 | /// 272 | /// Creates a complex type with the specified kind and type. 273 | /// 274 | /// The kind of the complex type. 275 | /// The type of the complex type. 276 | /// A complex type with the specified kind and type. 277 | public static PortableComplexType CreateInlineTokenOperand(PortableComplexTypeKind kind, PortableComplexType type) { 278 | if (kind < PortableComplexTypeKind.InlineType || kind > PortableComplexTypeKind.InlineMethod) 279 | throw new ArgumentOutOfRangeException(nameof(kind)); 280 | return new PortableComplexType(kind, default, 0, [type]); 281 | } 282 | 283 | /// 284 | /// Parses the specified string and returns a complex type. 285 | /// 286 | /// The string to parse. 287 | /// A complex type parsed from the specified string. 288 | public static PortableComplexType Parse(string s) { 289 | if (string.IsNullOrEmpty(s)) 290 | throw new ArgumentException($"'{nameof(s)}' cannot be null or empty.", nameof(s)); 291 | 292 | return PortableComplexTypeFormatter.Parse(s); 293 | } 294 | 295 | /// 296 | /// Returns a string representation of the complex type. 297 | /// 298 | /// A string representation of the complex type. 299 | public override readonly string ToString() { 300 | return PortableComplexTypeFormatter.ToString(this); 301 | } 302 | } 303 | 304 | /// 305 | /// Represents a portable constant. 306 | /// 307 | /// 308 | /// 309 | public struct PortableConstant(int type, object? value) { 310 | /// 311 | /// Gets or sets the type of the constant. 312 | /// 313 | public int Type { get; set; } = type; 314 | 315 | /// 316 | /// Gets or sets the value of the constant. 317 | /// 318 | /// 319 | /// The value can be of type , , , , 320 | /// , , , , , 321 | /// , , , or . 322 | /// 323 | public object? Value { get; set; } = value; 324 | 325 | /// 326 | [Obsolete("Reserved for deserialization.")] 327 | public long? PrimitiveValue { 328 | readonly get => PrimitivesHelper.ToSlot(Value); 329 | set { 330 | if (PrimitivesHelper.FromSlot(value, Type) is object slot) 331 | Value = slot; 332 | } 333 | } 334 | 335 | /// 336 | [Obsolete("Reserved for deserialization.")] 337 | public string? StringValue { 338 | readonly get => Value as string; 339 | set { 340 | if (Type == 0x0E && value is string s) 341 | Value = s; 342 | } 343 | } 344 | 345 | /// 346 | /// Returns the string of the constant value. 347 | /// 348 | /// 349 | public override readonly string? ToString() { 350 | return Value?.ToString(); 351 | } 352 | } 353 | 354 | /// 355 | /// Represents a generic parameter. 356 | /// 357 | /// The name of the generic parameter. 358 | /// The attributes of the generic parameter. 359 | /// The number of the generic parameter. 360 | /// The constraints of the generic parameter. 361 | public struct PortableGenericParameter(string name, int attributes, int number, IList? constraints) { 362 | /// 363 | /// Gets or sets the name of the generic parameter. 364 | /// 365 | public string Name { get; set; } = name; 366 | 367 | /// 368 | /// Gets or sets the attributes of the generic parameter. 369 | /// 370 | public int Attributes { get; set; } = attributes; 371 | 372 | /// 373 | /// Gets or sets the number of the generic parameter. 374 | /// 375 | public int Number { get; set; } = number; 376 | 377 | /// 378 | /// Gets or sets the constraints of the generic parameter. 379 | /// 380 | /// TypeDefOrRef 381 | public IList? Constraints { get; set; } = constraints; 382 | 383 | /// 384 | /// Returns the string representation of the generic parameter. 385 | /// 386 | /// The name of the generic parameter. 387 | public override readonly string ToString() { 388 | return Name; 389 | } 390 | } 391 | 392 | /// 393 | /// Represents a custom attribute. 394 | /// 395 | public struct PortableCustomAttribute(PortableToken constructor, byte[] rawData) { 396 | /// 397 | /// Gets or sets the constructor of the custom attribute. 398 | /// 399 | /// MethodDefOrRef 400 | public PortableToken Constructor { get; set; } = constructor; 401 | 402 | /// 403 | /// Gets or sets the raw data of the custom attribute. 404 | /// 405 | public byte[] RawData { get; set; } = rawData ?? throw new ArgumentNullException(nameof(rawData)); 406 | } 407 | 408 | static class PortableComplexTypeFormatter { 409 | const char TokenNameQuote = '\''; 410 | 411 | #region Parse 412 | public static PortableComplexType Parse(string s) { 413 | if (string.IsNullOrEmpty(s)) 414 | throw new ArgumentException($"'{nameof(s)}' cannot be null or empty.", nameof(s)); 415 | 416 | int index = 0; 417 | return ReadType(s, ref index); 418 | } 419 | 420 | static PortableComplexType ReadType(string s, ref int index) { 421 | var next = ReadNext(s, ref index); 422 | if (TryParseToken(next, out var token)) 423 | return PortableComplexType.CreateToken(token); 424 | if (EnumTryParse(next, out var et)) 425 | return PortableComplexType.CreateTypeSig((byte)et, ReadElementTypeArgs(s, ref index, et)); 426 | if (EnumTryParse(next, out var cc)) 427 | return PortableComplexType.CreateCallingConventionSig((byte)cc, ReadCallingConventionArgs(s, ref index, cc)); 428 | if (EnumTryParse(next, out var st)) 429 | return ReadSpecialType(s, ref index, st); 430 | throw new InvalidDataException($"Invalid complex type beginning: {next}."); 431 | 432 | #if NETFRAMEWORK && !NET40_OR_GREATER 433 | static bool EnumTryParse(string value, out TEnum result) where TEnum : struct { 434 | if (Enum.IsDefined(typeof(TEnum), value)) { 435 | result = (TEnum)Enum.Parse(typeof(TEnum), value); 436 | return true; 437 | } 438 | else { 439 | result = default; 440 | return false; 441 | } 442 | } 443 | #else 444 | static bool EnumTryParse(string value, out TEnum result) where TEnum : struct { 445 | return Enum.TryParse(value, out result); 446 | } 447 | #endif 448 | } 449 | 450 | static bool TryParseToken(string s, out PortableToken token) { 451 | if (s.Length > 2 && s[0] == TokenNameQuote && s[s.Length - 1] == TokenNameQuote) { 452 | token = s.Substring(1, s.Length - 2); 453 | return true; 454 | } 455 | if (int.TryParse(s, out int index)) { 456 | token = index; 457 | return true; 458 | } 459 | token = default; 460 | return false; 461 | } 462 | 463 | static List? ReadElementTypeArgs(string s, ref int index, ElementType2 elementType) { 464 | switch (elementType) { 465 | case ElementType2.End: 466 | case ElementType2.Void: 467 | case ElementType2.Boolean: 468 | case ElementType2.Char: 469 | case ElementType2.I1: 470 | case ElementType2.U1: 471 | case ElementType2.I2: 472 | case ElementType2.U2: 473 | case ElementType2.I4: 474 | case ElementType2.U4: 475 | case ElementType2.I8: 476 | case ElementType2.U8: 477 | case ElementType2.R4: 478 | case ElementType2.R8: 479 | case ElementType2.String: 480 | case ElementType2.TypedByRef: 481 | case ElementType2.I: 482 | case ElementType2.U: 483 | case ElementType2.R: 484 | case ElementType2.Object: 485 | case ElementType2.Sentinel: 486 | return null; 487 | } 488 | 489 | if (!ReadUntil(s, ref index, '(')) 490 | throw new InvalidDataException("Can't find '('."); 491 | 492 | List arguments; 493 | switch (elementType) { 494 | case ElementType2.Ptr: 495 | case ElementType2.ByRef: 496 | case ElementType2.FnPtr: 497 | case ElementType2.SZArray: 498 | case ElementType2.Pinned: 499 | // et(next) 500 | arguments = [ReadType(s, ref index)]; 501 | break; 502 | 503 | case ElementType2.ValueType: 504 | case ElementType2.Class: 505 | // et(next) 506 | arguments = [ReadType(s, ref index)]; 507 | break; 508 | 509 | case ElementType2.Var: 510 | case ElementType2.MVar: 511 | // et(index) 512 | arguments = [ReadInt32(s, ref index)]; 513 | break; 514 | 515 | case ElementType2.Array: { 516 | // et(next, rank, numSizes, .. sizes, numLowerBounds, .. lowerBounds) 517 | arguments = []; 518 | var nextType = ReadType(s, ref index); 519 | arguments.Add(nextType); 520 | arguments.Add(ReadInt32(s, ref index, out int rank)); 521 | arguments.Add(ReadInt32(s, ref index, out int numSizes)); 522 | for (int i = 0; i < numSizes; i++) 523 | arguments.Add(ReadInt32(s, ref index)); 524 | arguments.Add(ReadInt32(s, ref index, out int numLowerBounds)); 525 | for (int i = 0; i < numLowerBounds; i++) 526 | arguments.Add(ReadInt32(s, ref index)); 527 | break; 528 | } 529 | 530 | case ElementType2.GenericInst: { 531 | // et(next, num, .. args) 532 | arguments = []; 533 | var nextType = ReadType(s, ref index); 534 | arguments.Add(nextType); 535 | arguments.Add(ReadInt32(s, ref index, out int num)); 536 | for (int i = 0; i < num; i++) 537 | arguments.Add(ReadType(s, ref index)); 538 | break; 539 | } 540 | 541 | case ElementType2.ValueArray: 542 | // et(next, size) 543 | arguments = [ReadType(s, ref index), ReadInt32(s, ref index)]; 544 | break; 545 | 546 | case ElementType2.CModReqd: 547 | case ElementType2.CModOpt: 548 | // et(modifier, next) 549 | arguments = [ReadType(s, ref index), ReadType(s, ref index)]; 550 | break; 551 | 552 | case ElementType2.Module: 553 | // et(index, next) 554 | arguments = [ReadInt32(s, ref index), ReadType(s, ref index)]; 555 | break; 556 | 557 | default: 558 | throw new InvalidOperationException(); 559 | } 560 | 561 | if (!ReadUntil(s, ref index, ')')) 562 | throw new InvalidDataException("Can't find ')'."); 563 | 564 | return arguments; 565 | } 566 | 567 | static List ReadCallingConventionArgs(string s, ref int index, CallingConvention callingConvention) { 568 | if (!ReadUntil(s, ref index, '(')) 569 | throw new InvalidDataException("Can't find '('."); 570 | 571 | List arguments = [ReadInt32(s, ref index, out int flags)]; 572 | switch (callingConvention) { 573 | case CallingConvention.Default: 574 | case CallingConvention.C: 575 | case CallingConvention.StdCall: 576 | case CallingConvention.ThisCall: 577 | case CallingConvention.FastCall: 578 | case CallingConvention.VarArg: 579 | case CallingConvention.Property: 580 | case CallingConvention.Unmanaged: 581 | case CallingConvention.NativeVarArg: 582 | // cc([numGPs], numParams, retType, .. params) 583 | bool generic = (flags & 0x10) != 0; 584 | if (generic) // GenericParam count 585 | arguments.Add(ReadInt32(s, ref index)); 586 | arguments.Add(ReadInt32(s, ref index, out int parameterCount)); 587 | var returnType = ReadType(s, ref index); 588 | arguments.Add(returnType); 589 | for (int i = 0; i < parameterCount; i++) { 590 | var sig = ReadType(s, ref index); 591 | if (sig.Type == (byte)ElementType2.Sentinel) { 592 | i--; 593 | continue; 594 | } 595 | arguments.Add(sig); 596 | } 597 | break; 598 | 599 | case CallingConvention.Field: 600 | // cc(fieldType) 601 | arguments.Add(ReadType(s, ref index)); 602 | break; 603 | 604 | case CallingConvention.LocalSig: 605 | // cc(numLocals, .. locals) 606 | arguments.Add(ReadInt32(s, ref index, out int numLocals)); 607 | for (int i = 0; i < numLocals; i++) 608 | arguments.Add(ReadType(s, ref index)); 609 | break; 610 | 611 | case CallingConvention.GenericInstCC: 612 | // cc(numArgs, .. args) 613 | arguments.Add(ReadInt32(s, ref index, out int numArgs)); 614 | for (int i = 0; i < numArgs; i++) 615 | arguments.Add(ReadType(s, ref index)); 616 | break; 617 | 618 | default: 619 | throw new InvalidOperationException(); 620 | } 621 | 622 | if (!ReadUntil(s, ref index, ')')) 623 | throw new InvalidDataException("Can't find ')'."); 624 | 625 | return arguments; 626 | } 627 | 628 | static PortableComplexType ReadSpecialType(string s, ref int index, SpecialType specialType) { 629 | if (!ReadUntil(s, ref index, '(')) 630 | throw new InvalidDataException("Can't find '('."); 631 | 632 | PortableComplexType type; 633 | switch (specialType) { 634 | case SpecialType.Int32: 635 | type = PortableComplexType.CreateInt32(int.Parse(ReadNext(s, ref index))); 636 | break; 637 | case SpecialType.MethodSpec: 638 | type = PortableComplexType.CreateMethodSpec(ReadType(s, ref index), ReadType(s, ref index)); 639 | break; 640 | case SpecialType.InlineType: 641 | case SpecialType.InlineField: 642 | case SpecialType.InlineMethod: 643 | type = PortableComplexType.CreateInlineTokenOperand(PortableComplexTypeKind.Int32 + (byte)specialType, ReadType(s, ref index)); 644 | break; 645 | default: 646 | throw new InvalidOperationException(); 647 | } 648 | 649 | if (!ReadUntil(s, ref index, ')')) 650 | throw new InvalidDataException("Can't find ')'."); 651 | 652 | return type; 653 | } 654 | 655 | static PortableComplexType ReadInt32(string s, ref int index) { 656 | return ReadInt32(s, ref index, out _); 657 | } 658 | 659 | static PortableComplexType ReadInt32(string s, ref int index, out int value) { 660 | var type = ReadType(s, ref index); 661 | value = type.GetInt32(); 662 | return type; 663 | } 664 | 665 | static string ReadNext(string s, ref int index) { 666 | var sb = new StringBuilder(); 667 | while (index < s.Length) { 668 | var c = s[index++]; 669 | if (c == ',') { 670 | if (sb.Length == 0) 671 | continue; // Skip leading commas 672 | break; 673 | } 674 | if (c == '(' || c == ')') { 675 | index--; 676 | break; 677 | } 678 | sb.Append(c); 679 | } 680 | Debug.Assert(sb.Length != 0); 681 | var result = sb.ToString(); 682 | return result; 683 | } 684 | 685 | static bool ReadUntil(string s, ref int index, char c) { 686 | while (index < s.Length) { 687 | if (s[index++] == c) 688 | return true; 689 | } 690 | return false; 691 | } 692 | #endregion 693 | 694 | #region ToString 695 | public static string ToString(PortableComplexType type) { 696 | var sb = new StringBuilder(); 697 | WriteType(type, sb); 698 | return sb.ToString(); 699 | } 700 | 701 | static void WriteType(PortableComplexType type, StringBuilder sb) { 702 | switch (type.Kind) { 703 | case PortableComplexTypeKind.Token: 704 | if (type.Token.Name is string name) 705 | sb.Append(TokenNameQuote).Append(name).Append(TokenNameQuote); 706 | else 707 | sb.Append(type.Token.Index); 708 | return; 709 | case PortableComplexTypeKind.TypeSig: 710 | var elementType = (ElementType2)type.Type; 711 | if (!Enum.IsDefined(typeof(ElementType2), elementType)) 712 | throw new InvalidDataException($"Invalid element type: {type.Type}."); 713 | sb.Append(elementType.ToString()); 714 | WriteArgs(type.Arguments, sb); 715 | return; 716 | case PortableComplexTypeKind.CallingConventionSig: 717 | var callingConvention = (CallingConvention)type.Type; 718 | if (!Enum.IsDefined(typeof(CallingConvention), callingConvention)) 719 | throw new InvalidDataException($"Invalid calling convention: {type.Type}."); 720 | sb.Append(callingConvention.ToString()); 721 | WriteArgs(type.Arguments, sb); 722 | return; 723 | default: 724 | var specialType = (SpecialType)(type.Kind - PortableComplexTypeKind.Int32); 725 | if (!Enum.IsDefined(typeof(SpecialType), specialType)) 726 | throw new InvalidDataException($"Invalid complex type kind: {type.Kind}."); 727 | sb.Append(specialType); 728 | if (specialType == SpecialType.Int32) 729 | sb.Append('(').Append(type.GetInt32()).Append(')'); 730 | else 731 | WriteArgs(type.Arguments, sb); 732 | return; 733 | } 734 | } 735 | 736 | static void WriteArgs(IList? arguments, StringBuilder sb) { 737 | Debug.Assert(arguments is null || arguments.Count != 0, "Arguments should be null if empty."); 738 | if (arguments is null || arguments.Count == 0) 739 | return; 740 | sb.Append('('); 741 | foreach (var arg in arguments) { 742 | WriteType(arg, sb); 743 | sb.Append(','); 744 | } 745 | sb.Remove(sb.Length - 1, 1); 746 | sb.Append(')'); 747 | } 748 | #endregion 749 | 750 | internal static PortableToken? GetScopeType(PortableComplexType type) { 751 | if (type.Kind != PortableComplexTypeKind.TypeSig) 752 | return null; 753 | 754 | var args = type.Arguments; 755 | switch ((ElementType2)type.Type) { 756 | case ElementType2.Ptr: 757 | case ElementType2.ByRef: 758 | case ElementType2.Array: 759 | case ElementType2.GenericInst: 760 | case ElementType2.ValueArray: 761 | case ElementType2.SZArray: 762 | case ElementType2.Pinned: 763 | return args is not null ? GetScopeType(args[0]) : null; 764 | 765 | case ElementType2.ValueType: 766 | case ElementType2.Class: 767 | return args is not null && args[0].Kind == PortableComplexTypeKind.Token ? args[0].Token : default(PortableToken?); 768 | 769 | case ElementType2.CModReqd: 770 | case ElementType2.CModOpt: 771 | return args is not null ? GetScopeType(args[1]) : null; 772 | 773 | default: 774 | return null; 775 | } 776 | } 777 | 778 | internal enum ElementType : byte { 779 | End = 0x00, 780 | Void = 0x01, 781 | Boolean = 0x02, 782 | Char = 0x03, 783 | I1 = 0x04, 784 | U1 = 0x05, 785 | I2 = 0x06, 786 | U2 = 0x07, 787 | I4 = 0x08, 788 | U4 = 0x09, 789 | I8 = 0x0A, 790 | U8 = 0x0B, 791 | R4 = 0x0C, 792 | R8 = 0x0D, 793 | String = 0x0E, 794 | Ptr = 0x0F, 795 | ByRef = 0x10, 796 | ValueType = 0x11, 797 | Class = 0x12, 798 | Var = 0x13, 799 | Array = 0x14, 800 | GenericInst = 0x15, 801 | TypedByRef = 0x16, 802 | ValueArray = 0x17, 803 | I = 0x18, 804 | U = 0x19, 805 | R = 0x1A, 806 | FnPtr = 0x1B, 807 | Object = 0x1C, 808 | SZArray = 0x1D, 809 | MVar = 0x1E, 810 | CModReqd = 0x1F, 811 | CModOpt = 0x20, 812 | Module = 0x3F, 813 | Sentinel = 0x41, 814 | Pinned = 0x45 815 | } 816 | 817 | enum CallingConvention : byte { 818 | Default = 0x0, 819 | C = 0x1, 820 | StdCall = 0x2, 821 | ThisCall = 0x3, 822 | FastCall = 0x4, 823 | VarArg = 0x5, 824 | Field = 0x6, 825 | LocalSig = 0x7, 826 | Property = 0x8, 827 | Unmanaged = 0x9, 828 | GenericInstCC = 0xA, 829 | NativeVarArg = 0xB 830 | } 831 | 832 | enum SpecialType : byte { 833 | Int32, 834 | MethodSpec, 835 | InlineType, 836 | InlineField, 837 | InlineMethod 838 | } 839 | } 840 | 841 | /// 842 | /// A helper to convert primitive values. 843 | /// 844 | public static class PrimitivesHelper { 845 | /// 846 | /// Converts the specified value to a slot representation. 847 | /// 848 | /// The value to convert. 849 | /// The slot representation of the value. 850 | public static long? ToSlot(object? value) { 851 | switch (value) { 852 | case null: 853 | default: 854 | return null; 855 | case bool b: 856 | return b ? 1 : 0; 857 | case char c: 858 | return c; 859 | case sbyte i1: 860 | return i1; 861 | case byte u1: 862 | return u1; 863 | case short i2: 864 | return i2; 865 | case ushort u2: 866 | return u2; 867 | case int i4: 868 | return i4; 869 | case uint u4: 870 | return u4; 871 | case long i8: 872 | return i8; 873 | case ulong u8: 874 | return (long)u8; 875 | case float r4: 876 | long t = BitConverter.DoubleToInt64Bits(r4); 877 | Debug.Assert(!float.IsNaN(r4) || (ulong)t == 0xFFF8000000000000, "Casting a NaN float to double will lost information."); 878 | return t; 879 | case double r8: 880 | return BitConverter.DoubleToInt64Bits(r8); 881 | } 882 | } 883 | 884 | /// 885 | /// Converts the specified slot representation to a value of the specified element type. 886 | /// 887 | /// The slot representation to convert. 888 | /// The element type of the value. 889 | /// The value of the specified element type. 890 | public static object? FromSlot(long? value, int elementType) { 891 | if (value is not long slot) 892 | return null; 893 | var elementType2 = (ElementType2)elementType; 894 | switch (elementType2) { 895 | case ElementType2.Boolean: 896 | return slot != 0; 897 | case ElementType2.Char: 898 | return (char)slot; 899 | case ElementType2.I1: 900 | return (sbyte)slot; 901 | case ElementType2.U1: 902 | return (byte)slot; 903 | case ElementType2.I2: 904 | return (short)slot; 905 | case ElementType2.U2: 906 | return (ushort)slot; 907 | case ElementType2.I4: 908 | return (int)slot; 909 | case ElementType2.U4: 910 | return (uint)slot; 911 | case ElementType2.I8: 912 | return slot; 913 | case ElementType2.U8: 914 | return (ulong)slot; 915 | case ElementType2.R4: 916 | double t = BitConverter.Int64BitsToDouble(slot); 917 | Debug.Assert(!double.IsNaN(t) || t == 0xFFF8000000000000, "Casting a NaN double to float will lost information."); 918 | return (float)t; 919 | case ElementType2.R8: 920 | return BitConverter.Int64BitsToDouble(slot); 921 | default: 922 | Debug.Assert(Enum.IsDefined(typeof(ElementType2), elementType2), "Invalid element type."); 923 | return null; 924 | } 925 | } 926 | } 927 | -------------------------------------------------------------------------------- /PortableMetadata/PortableField.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MetadataSerialization; 4 | 5 | /// 6 | /// Represents a portable field. 7 | /// 8 | /// The name of the field. 9 | /// The declaring type of the field. 10 | /// The signature of the field. 11 | public class PortableField(string name, PortableComplexType type, PortableComplexType signature) { 12 | /// 13 | /// Gets or sets the name of the field. 14 | /// 15 | public string Name { get; set; } = name; 16 | 17 | /// 18 | /// Gets or sets the declaring type of the field. 19 | /// 20 | /// TypeDefOrRef 21 | public PortableComplexType Type { get; set; } = type; 22 | 23 | /// 24 | /// Gets or sets the signature of the field. 25 | /// 26 | /// FieldSig 27 | public PortableComplexType Signature { get; set; } = signature; 28 | 29 | /// 30 | /// Returns the name of the field. 31 | /// 32 | /// The name of the field. 33 | public override string ToString() { 34 | return Name; 35 | } 36 | } 37 | 38 | /// 39 | /// Represents a portable field definition. 40 | /// 41 | /// The name of the field. 42 | /// The declaring type of the field. 43 | /// The signature of the field. 44 | /// The attributes of the field. 45 | /// The initial value of the field. 46 | /// The constant value of the field. 47 | /// The custom attributes of the field. 48 | public sealed class PortableFieldDef(string name, PortableComplexType type, PortableComplexType signature, int attributes, 49 | byte[]? initialValue, PortableConstant? constant, IList? customAttributes) 50 | : PortableField(name, type, signature) { 51 | /// 52 | /// Gets or sets the attributes of the field. 53 | /// 54 | public int Attributes { get; set; } = attributes; 55 | 56 | /// 57 | /// Gets or sets the initial value of the field. 58 | /// 59 | public byte[]? InitialValue { get; set; } = initialValue; 60 | 61 | /// 62 | /// Gets or sets the constant value of the field. 63 | /// 64 | public PortableConstant? Constant { get; set; } = constant; 65 | 66 | /// 67 | /// Gets or sets the custom attributes of the field. 68 | /// 69 | public IList? CustomAttributes { get; set; } = customAttributes; 70 | } 71 | -------------------------------------------------------------------------------- /PortableMetadata/PortableMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Text; 7 | 8 | namespace MetadataSerialization; 9 | 10 | /// 11 | /// The options to create the portable metadata. 12 | /// 13 | [Flags] 14 | public enum PortableMetadataOptions { 15 | /// 16 | /// None 17 | /// 18 | None = 0, 19 | 20 | /// 21 | /// Use type/member name as token instead of index. This option will make the output more readable but significantly increase the size. 22 | /// 23 | UseNamedToken = 1, 24 | 25 | /// 26 | /// Use assembly full name not the display name. 27 | /// 28 | UseAssemblyFullName = 2, 29 | 30 | /// 31 | /// Include method bodies. 32 | /// 33 | IncludeMethodBodies = 4, 34 | 35 | /// 36 | /// Include custom attributes. 37 | /// 38 | IncludeCustomAttributes = 8, 39 | 40 | /// 41 | /// The original method body's max stack field should be used and a new one should not be calculated. 42 | /// 43 | KeepOldMaxStack = 16 44 | } 45 | 46 | /// 47 | /// Portable metadata 48 | /// 49 | /// The options of the portable metadata. 50 | public sealed class PortableMetadata(PortableMetadataOptions options) { 51 | /// 52 | /// The default options of the portable metadata. 53 | /// 54 | public const PortableMetadataOptions DefaultOptions = PortableMetadataOptions.UseAssemblyFullName | PortableMetadataOptions.IncludeMethodBodies | PortableMetadataOptions.IncludeCustomAttributes; 55 | 56 | /// 57 | /// Gets the options of the portable metadata. 58 | /// 59 | public PortableMetadataOptions Options { get; } = options; 60 | 61 | /// 62 | /// Gets the dictionary of portable types. 63 | /// 64 | public IDictionary Types { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0); 65 | 66 | /// 67 | /// Gets the dictionary of portable fields. 68 | /// 69 | public IDictionary Fields { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0); 70 | 71 | /// 72 | /// Gets the dictionary of portable methods. 73 | /// 74 | public IDictionary Methods { get; } = CreateDictionary((options & PortableMetadataOptions.UseNamedToken) != 0); 75 | 76 | static IDictionary CreateDictionary(bool useNamedToken) where TValue : class { 77 | return useNamedToken ? new Dictionary() : new ListToDictionaryAdapter(); 78 | } 79 | 80 | [DebuggerDisplay("Count = {Count}")] 81 | [DebuggerTypeProxy(typeof(ListToDictionaryAdapter_CollectionDebugView<>))] 82 | sealed class ListToDictionaryAdapter : IDictionary, IDictionary where TValue : class { 83 | readonly List values = []; 84 | readonly List keys = []; 85 | 86 | public TValue this[PortableToken key] { 87 | get => values[GetIndex(key)]; 88 | set => Add(key, value, true); 89 | } 90 | 91 | public ICollection Keys => keys.AsReadOnly(); 92 | 93 | public ICollection Values => values.AsReadOnly(); 94 | 95 | public int Count => values.Count; 96 | 97 | public bool IsReadOnly => false; 98 | 99 | public void Add(PortableToken key, TValue value) { 100 | Add(key, value, false); 101 | } 102 | 103 | void Add(PortableToken key, TValue value, bool inserting) { 104 | if (value is null) 105 | throw new ArgumentNullException(nameof(value)); 106 | 107 | int index = GetIndex(key); 108 | if (index > values.Count) 109 | throw new ArgumentException("Key is out of range", nameof(key)); 110 | 111 | if (index == values.Count) { 112 | values.Add(value); 113 | keys.Add(index); 114 | return; 115 | } 116 | 117 | if (!inserting) 118 | throw new ArgumentException("Key already exists", nameof(key)); 119 | values[index] = value; 120 | } 121 | 122 | void ICollection>.Add(KeyValuePair item) { 123 | Add(item.Key, item.Value); 124 | } 125 | 126 | public void Clear() { 127 | values.Clear(); 128 | keys.Clear(); 129 | } 130 | 131 | bool ICollection>.Contains(KeyValuePair item) { 132 | return ContainsKey(item.Key) && EqualityComparer.Default.Equals(values[GetIndex(item.Key)], item.Value); 133 | } 134 | 135 | public bool ContainsKey(PortableToken key) { 136 | return GetIndex(key) < values.Count; 137 | } 138 | 139 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { 140 | if (array is null) 141 | throw new ArgumentNullException(nameof(array)); 142 | if (arrayIndex < 0 || arrayIndex >= array.Length) 143 | throw new ArgumentOutOfRangeException(nameof(arrayIndex)); 144 | if (array.Length - arrayIndex < values.Count) 145 | throw new ArgumentException("Insufficient space in destination array", nameof(array)); 146 | 147 | for (int i = 0; i < values.Count; i++) 148 | array[arrayIndex + i] = new KeyValuePair(keys[i], values[i]); 149 | } 150 | 151 | public IEnumerator> GetEnumerator() { 152 | int oldCount = values.Count; 153 | for (int i = 0; i < oldCount; i++) { 154 | if (values.Count != oldCount) 155 | throw new InvalidOperationException("Collection was modified"); 156 | yield return new KeyValuePair(keys[i], values[i]); 157 | } 158 | } 159 | 160 | public bool Remove(PortableToken key) { 161 | throw new NotSupportedException(); 162 | } 163 | 164 | bool ICollection>.Remove(KeyValuePair item) { 165 | throw new NotImplementedException(); 166 | } 167 | 168 | #pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). 169 | public bool TryGetValue(PortableToken key, [MaybeNullWhen(false)] out TValue value) { 170 | #pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). 171 | int index = GetIndex(key); 172 | if (index >= values.Count) { 173 | value = default; 174 | return false; 175 | } 176 | value = values[index]; 177 | return true; 178 | } 179 | 180 | IEnumerator IEnumerable.GetEnumerator() { 181 | return GetEnumerator(); 182 | } 183 | 184 | static int GetIndex(PortableToken token) { 185 | if (token.Name is not null) 186 | throw new ArgumentException("Token is named.", nameof(token)); 187 | int index = token.Index; 188 | if (index < 0) 189 | throw new ArgumentOutOfRangeException(nameof(token)); 190 | return index; 191 | } 192 | 193 | #region IDictionary 194 | ICollection IDictionary.Keys => (ICollection)Keys; 195 | ICollection IDictionary.Values => (ICollection)Values; 196 | bool IDictionary.IsFixedSize => false; 197 | object ICollection.SyncRoot => values; 198 | bool ICollection.IsSynchronized => false; 199 | object? IDictionary.this[object key] { get => this[(PortableToken)key]; set => this[(PortableToken)key] = (TValue)value!; } 200 | bool IDictionary.Contains(object key) { return ContainsKey((PortableToken)key); } 201 | void IDictionary.Add(object key, object? value) { Add((PortableToken)key, (TValue)value!); } 202 | IDictionaryEnumerator IDictionary.GetEnumerator() { return new DictionaryEnumerator(GetEnumerator()); } 203 | void IDictionary.Remove(object key) { Remove((PortableToken)key); } 204 | void ICollection.CopyTo(Array array, int index) { ((ICollection>)this).CopyTo((KeyValuePair[])array, index); } 205 | sealed class DictionaryEnumerator(IEnumerator> enumerator) : IDictionaryEnumerator { 206 | public object Current => Entry; 207 | public object Key => enumerator.Current.Key; 208 | public object Value => enumerator.Current.Value; 209 | public DictionaryEntry Entry => new(enumerator.Current.Key, enumerator.Current.Value); 210 | public bool MoveNext() { return enumerator.MoveNext(); } 211 | public void Reset() { enumerator.Reset(); } 212 | } 213 | #endregion 214 | } 215 | 216 | sealed class ListToDictionaryAdapter_CollectionDebugView(ListToDictionaryAdapter dictionary) where TValue : class { 217 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 218 | public TValue[] Items => [.. dictionary.Values]; 219 | } 220 | } 221 | 222 | /// 223 | /// Represents a facade for portable metadata. 224 | /// 225 | /// 226 | public sealed class PortableMetadataFacade(PortableMetadata? metadata) { 227 | /// 228 | /// Represents a facade for accessing portable metadata. 229 | /// 230 | /// The type of the portable entity. 231 | /// The type of the portable entity definition. 232 | public struct EntityFacade where T : class where TDef : T { 233 | /// 234 | /// Gets or sets the dictionary of portable entity references. 235 | /// 236 | public IDictionary References { get; set; } 237 | 238 | /// 239 | /// Gets or sets the dictionary of portable entity definitions. 240 | /// 241 | public IDictionary Definitions { get; set; } 242 | 243 | /// 244 | /// Gets or sets the list of orders for portable entities. 245 | /// 246 | public IList Orders { get; set; } 247 | } 248 | 249 | /// 250 | /// Gets or sets the options of the portable metadata. 251 | /// 252 | public PortableMetadataOptions Options { get; set; } = metadata?.Options ?? 0; 253 | 254 | /// 255 | /// Gets or sets the entity facade for portable types. 256 | /// 257 | public EntityFacade Types { get; set; } = Copy(metadata?.Types, metadata?.Options ?? 0); 258 | 259 | /// 260 | /// Gets or sets the entity facade for portable fields. 261 | /// 262 | public EntityFacade Fields { get; set; } = Copy(metadata?.Fields, metadata?.Options ?? 0); 263 | 264 | /// 265 | /// Gets or sets the entity facade for portable methods. 266 | /// 267 | public EntityFacade Methods { get; set; } = Copy(metadata?.Methods, metadata?.Options ?? 0); 268 | 269 | /// 270 | /// The constructor for deserialization. 271 | /// 272 | public PortableMetadataFacade() : this(null) { } 273 | 274 | static EntityFacade Copy(IDictionary? source, PortableMetadataOptions useNamedToken) where T : class where TDef : T { 275 | var result = new EntityFacade { 276 | References = new Dictionary(), 277 | Definitions = new Dictionary(), 278 | Orders = [] 279 | }; 280 | if (source is null) 281 | return result; 282 | bool useNamedToken2 = (useNamedToken & PortableMetadataOptions.UseNamedToken) != 0; 283 | bool lastIsDef = false; 284 | int i = 0; 285 | foreach (var kvp in source) { 286 | var key = kvp.Key.ToString(); 287 | var value = kvp.Value; 288 | bool addOrder; 289 | if (value is TDef t) { 290 | result.Definitions.Add(key, t); 291 | addOrder = !lastIsDef; 292 | lastIsDef = true; 293 | } 294 | else { 295 | result.References.Add(key, value); 296 | addOrder = lastIsDef; 297 | lastIsDef = false; 298 | } 299 | if (addOrder && useNamedToken2) 300 | result.Orders.Add(i); 301 | i++; 302 | } 303 | return result; 304 | } 305 | 306 | /// 307 | /// Converts the to . 308 | /// 309 | /// The converted . 310 | public PortableMetadata ToMetadata() { 311 | var metadata = new PortableMetadata(Options); 312 | bool useNamedToken = (Options & PortableMetadataOptions.UseNamedToken) != 0; 313 | Copy(Types, metadata.Types, useNamedToken); 314 | Copy(Fields, metadata.Fields, useNamedToken); 315 | Copy(Methods, metadata.Methods, useNamedToken); 316 | return metadata; 317 | } 318 | 319 | static void Copy(EntityFacade source, IDictionary destination, bool useNamedToken) where T : class where TDef : T { 320 | int count = source.References.Count + source.Definitions.Count; 321 | if (useNamedToken) { 322 | using var orders = source.Orders.GetEnumerator(); 323 | using var refs = source.References.GetEnumerator(); 324 | using var defs = source.Definitions.GetEnumerator(); 325 | orders.MoveNext(); 326 | bool lastIsDef = false; 327 | for (int i = 0; i < count; i++) { 328 | if (i == orders.Current) { 329 | lastIsDef = !lastIsDef; 330 | orders.MoveNext(); 331 | } 332 | string key; 333 | T value; 334 | if (lastIsDef) { 335 | defs.MoveNext(); 336 | key = defs.Current.Key; 337 | value = defs.Current.Value; 338 | } 339 | else { 340 | refs.MoveNext(); 341 | key = refs.Current.Key; 342 | value = refs.Current.Value; 343 | } 344 | destination.Add(key, value); 345 | } 346 | } 347 | else { 348 | for (int i = 0; i < count; i++) { 349 | var key = i.ToString(); 350 | var value = source.References.TryGetValue(key, out var t) ? t : source.Definitions[key]; 351 | destination.Add(i, value); 352 | } 353 | } 354 | } 355 | } 356 | 357 | /// 358 | /// Represents the serialization level of the portable entity. 359 | /// 360 | public enum PortableMetadataLevel { 361 | /// 362 | /// Represents the reference level of the portable entity. 363 | /// 364 | Reference, 365 | 366 | /// 367 | /// Represents the definition level of the portable entity. 368 | /// 369 | Definition, 370 | 371 | /// 372 | /// Represents the definition with children level of the portable entity. 373 | /// 374 | DefinitionWithChildren 375 | } 376 | 377 | /// 378 | /// A helper type to create or update the portable metadata. 379 | /// 380 | /// The portable metadata. 381 | public sealed class PortableMetadataUpdater(PortableMetadata metadata) { 382 | sealed class TokenWithLevel { 383 | public PortableToken Token; 384 | public PortableMetadataLevel Level; 385 | } 386 | 387 | readonly Dictionary typeTokens = CreateTokenMap(metadata.Types); 388 | readonly Dictionary fieldTokens = CreateTokenMap(metadata.Fields); 389 | readonly Dictionary methodTokens = CreateTokenMap(metadata.Methods); 390 | 391 | bool UseNamedToken => (metadata.Options & PortableMetadataOptions.UseNamedToken) != 0; 392 | 393 | static Dictionary CreateTokenMap(IDictionary source) where T : class { 394 | var map = new Dictionary((IEqualityComparer)(object)PortableMetadataEqualityComparer.ReferenceComparer); 395 | foreach (var kvp in source) { 396 | var level = kvp.Value.GetType() == typeof(T) ? PortableMetadataLevel.Reference : PortableMetadataLevel.Definition; 397 | map.Add(kvp.Value, new TokenWithLevel { 398 | Token = kvp.Key, 399 | Level = level 400 | }); 401 | } 402 | return map; 403 | } 404 | 405 | /// 406 | /// Updates the portable type in the portable metadata. 407 | /// 408 | /// The portable type to update. 409 | /// The serialization level of the portable type. 410 | /// The current serialization level of the portable type. 411 | /// The updated portable type token. 412 | public PortableToken Update(PortableType type, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) { 413 | if (!typeTokens.TryGetValue(type, out var tl)) { 414 | tl = new TokenWithLevel { 415 | Token = UseNamedToken ? GetUniqueName(type.Name, s => metadata.Types.ContainsKey(s)) : metadata.Types.Count, 416 | Level = currentLevel = level 417 | }; 418 | typeTokens.Add(type, tl); 419 | metadata.Types.Add(tl.Token, type); 420 | } 421 | else if (level > tl.Level) { 422 | tl.Level = currentLevel = level; 423 | metadata.Types[tl.Token] = type; 424 | } 425 | else 426 | currentLevel = tl.Level; 427 | 428 | return tl.Token; 429 | } 430 | 431 | /// 432 | /// Updates the portable field in the portable metadata. 433 | /// 434 | /// The portable field to update. 435 | /// The serialization level of the portable field. 436 | /// The current serialization level of the portable field. 437 | /// The updated portable field token. 438 | public PortableToken Update(PortableField field, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) { 439 | if (!fieldTokens.TryGetValue(field, out var tl)) { 440 | tl = new TokenWithLevel { 441 | Token = UseNamedToken ? GetUniqueName(field.Type, field.Name, s => metadata.Fields.ContainsKey(s)) : metadata.Fields.Count, 442 | Level = currentLevel = level 443 | }; 444 | fieldTokens.Add(field, tl); 445 | metadata.Fields.Add(tl.Token, field); 446 | } 447 | else if (level > tl.Level) { 448 | tl.Level = currentLevel = level; 449 | metadata.Fields[tl.Token] = field; 450 | } 451 | else 452 | currentLevel = tl.Level; 453 | 454 | return tl.Token; 455 | } 456 | 457 | /// 458 | /// Updates the portable method in the portable metadata. 459 | /// 460 | /// The portable method to update. 461 | /// The serialization level of the portable method. 462 | /// The current serialization level of the portable method. 463 | /// The updated portable method token. 464 | public PortableToken Update(PortableMethod method, PortableMetadataLevel level, out PortableMetadataLevel currentLevel) { 465 | if (!methodTokens.TryGetValue(method, out var tl)) { 466 | tl = new TokenWithLevel { 467 | Token = UseNamedToken ? GetUniqueName(method.Type, method.Name, s => metadata.Methods.ContainsKey(s)) : metadata.Methods.Count, 468 | Level = currentLevel = level 469 | }; 470 | methodTokens.Add(method, tl); 471 | metadata.Methods.Add(tl.Token, method); 472 | } 473 | else if (level > tl.Level) { 474 | tl.Level = currentLevel = level; 475 | metadata.Methods[tl.Token] = method; 476 | } 477 | else 478 | currentLevel = tl.Level; 479 | 480 | return tl.Token; 481 | } 482 | 483 | static string GetUniqueName(PortableComplexType type, string baseName, Func hasName) { 484 | var typeName = type.Kind == PortableComplexTypeKind.Token ? type.Token.Name : PortableComplexTypeFormatter.GetScopeType(type)!.Value + ""; 485 | return GetUniqueName(typeName + "::" + baseName, hasName); 486 | } 487 | 488 | static string GetUniqueName(string baseName, Func hasName) { 489 | var sb = new StringBuilder(baseName); 490 | for (int i = 0; i < sb.Length; i++) { 491 | bool flag = false; 492 | foreach (char c in PortableToken.InvalidNameChars) { 493 | if (sb[i] == c) { 494 | flag = true; 495 | break; 496 | } 497 | } 498 | if (flag) 499 | sb[i] = '_'; 500 | } 501 | baseName = sb.ToString(); 502 | 503 | if (!hasName(baseName)) 504 | return baseName; 505 | 506 | for (int n = 2; n < int.MaxValue; n++) { 507 | var name = $"{baseName}_{n}"; 508 | if (!hasName(name)) 509 | return name; 510 | } 511 | 512 | throw new InvalidOperationException(); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /PortableMetadata/PortableMetadata.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net20;net40;netstandard2.0;net6.0;net8.0 4 | 12.0 5 | enable 6 | true 7 | 8 | 9 | -------------------------------------------------------------------------------- /PortableMetadata/PortableMetadataEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MetadataSerialization; 5 | 6 | /// 7 | /// The equality comparer for , , , and . 8 | /// 9 | public sealed class PortableMetadataEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer { 10 | readonly bool onlyReference; 11 | readonly bool nullEqualsEmpty; 12 | 13 | /// 14 | /// Gets the default equality comparer that compares only the reference. 15 | /// 16 | public static readonly PortableMetadataEqualityComparer ReferenceComparer = new(true, true); 17 | 18 | /// 19 | /// Gets the default equality comparer that compares all properties. 20 | /// 21 | public static readonly PortableMetadataEqualityComparer FullComparer = new(false, false); 22 | 23 | PortableMetadataEqualityComparer(bool onlyReference, bool nullEqualsEmpty) { 24 | this.onlyReference = onlyReference; 25 | this.nullEqualsEmpty = nullEqualsEmpty; 26 | } 27 | 28 | /// 29 | public bool Equals(PortableType? x, PortableType? y) { 30 | if (ReferenceEquals(x, y)) 31 | return true; 32 | if (x is null || y is null) 33 | return false; 34 | if (!onlyReference && x.GetType() != y.GetType()) 35 | return false; 36 | if (x.Name != y.Name || x.Namespace != y.Namespace || x.Assembly != y.Assembly || !Equals_NameList(x.EnclosingNames, y.EnclosingNames)) 37 | return false; 38 | if (onlyReference || (x is not PortableTypeDef && y is not PortableTypeDef)) 39 | return true; 40 | 41 | if (x is not PortableTypeDef xd || y is not PortableTypeDef yd) 42 | return false; 43 | if (xd.Attributes != yd.Attributes) 44 | return false; 45 | if (!Equals_ComplexType(xd.BaseType, yd.BaseType)) 46 | return false; 47 | 48 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes)) 49 | return false; 50 | if (!Equals_GenericParamList(xd.GenericParameters, yd.GenericParameters)) 51 | return false; 52 | if (!Equals_ComplexTypeList(xd.Interfaces, yd.Interfaces)) 53 | return false; 54 | if (!Equals_ClassLayout(xd.ClassLayout, yd.ClassLayout)) 55 | return false; 56 | 57 | if (!Equals_TokenList(xd.NestedTypes, yd.NestedTypes)) 58 | return false; 59 | if (!Equals_TokenList(xd.Fields, yd.Fields)) 60 | return false; 61 | if (!Equals_TokenList(xd.Methods, yd.Methods)) 62 | return false; 63 | if (!Equals_PropertyList(xd.Properties, yd.Properties)) 64 | return false; 65 | if (!Equals_EventList(xd.Events, yd.Events)) 66 | return false; 67 | return true; 68 | } 69 | 70 | /// 71 | public int GetHashCode(PortableType obj) { 72 | if (obj is null) 73 | return 0; 74 | return (((((obj.Name.GetHashCode() * -1521134295) + obj.Namespace.GetHashCode()) * -1521134295) + (obj.Assembly?.GetHashCode() ?? 0)) * -1521134295) + GetHashCode_NameList(obj.EnclosingNames); 75 | } 76 | 77 | /// 78 | public bool Equals(PortableField? x, PortableField? y) { 79 | if (ReferenceEquals(x, y)) 80 | return true; 81 | if (x is null || y is null) 82 | return false; 83 | if (!onlyReference && x.GetType() != y.GetType()) 84 | return false; 85 | if (x.Name != y.Name || !Equals(x.Type, y.Type) || !Equals(x.Signature, y.Signature)) 86 | return false; 87 | if (onlyReference || (x is not PortableFieldDef && y is not PortableFieldDef)) 88 | return true; 89 | 90 | if (x is not PortableFieldDef xd || y is not PortableFieldDef yd) 91 | return false; 92 | if (xd.Attributes != yd.Attributes) 93 | return false; 94 | if (!Equals_ByteArray(xd.InitialValue, yd.InitialValue)) 95 | return false; 96 | 97 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes)) 98 | return false; 99 | if (!Equals_Constant(xd.Constant, yd.Constant)) 100 | return false; 101 | return true; 102 | } 103 | 104 | /// 105 | public int GetHashCode(PortableField obj) { 106 | if (obj is null) 107 | return 0; 108 | return (((obj.Name.GetHashCode() * -1521134295) + GetHashCode(obj.Type)) * -1521134295) + GetHashCode(obj.Signature); 109 | } 110 | 111 | /// 112 | public bool Equals(PortableMethod? x, PortableMethod? y) { 113 | if (ReferenceEquals(x, y)) 114 | return true; 115 | if (x is null || y is null) 116 | return false; 117 | if (!onlyReference && x.GetType() != y.GetType()) 118 | return false; 119 | if (x.Name != y.Name || !Equals(x.Type, y.Type) || !Equals(x.Signature, y.Signature)) 120 | return false; 121 | if (onlyReference || (x is not PortableMethodDef && y is not PortableMethodDef)) 122 | return true; 123 | 124 | if (x is not PortableMethodDef xd || y is not PortableMethodDef yd) 125 | return false; 126 | if (xd.Attributes != yd.Attributes || xd.ImplAttributes != yd.ImplAttributes) 127 | return false; 128 | if (!Equals_ParamList(xd.Parameters, yd.Parameters)) 129 | return false; 130 | if (!Equals_MethodBody(xd.Body, yd.Body)) 131 | return false; 132 | 133 | if (!Equals_CustomAttributeList(xd.CustomAttributes, yd.CustomAttributes)) 134 | return false; 135 | if (!Equals_GenericParamList(xd.GenericParameters, yd.GenericParameters)) 136 | return false; 137 | if (!Equals_TokenList(xd.Overrides, yd.Overrides)) 138 | return false; 139 | if (!Equals_ImplMap(xd.ImplMap, yd.ImplMap)) 140 | return false; 141 | return true; 142 | } 143 | 144 | /// 145 | public int GetHashCode(PortableMethod obj) { 146 | if (obj is null) 147 | return 0; 148 | return (((obj.Name.GetHashCode() * -1521134295) + GetHashCode(obj.Type)) * -1521134295) + GetHashCode(obj.Signature); 149 | } 150 | 151 | /// 152 | public bool Equals(PortableComplexType x, PortableComplexType y) { 153 | return x.Kind == y.Kind && x.Token == y.Token && x.Type == y.Type && Equals_ComplexTypeList(x.Arguments, y.Arguments); 154 | } 155 | 156 | /// 157 | public int GetHashCode(PortableComplexType obj) { 158 | int hash = obj.Kind.GetHashCode(); 159 | hash = (hash * -1521134295) + obj.Token.GetHashCode(); 160 | hash = (hash * -1521134295) + obj.Type.GetHashCode(); 161 | var args = obj.Arguments; 162 | if (args is null) 163 | return hash; 164 | foreach (var arg in args) 165 | hash = (hash * -1521134295) + GetHashCode(arg); 166 | return hash; 167 | } 168 | 169 | bool Equals_ComplexTypeList(IList? x, IList? y) { 170 | if (x == y) 171 | return true; 172 | if (x is null || y is null) 173 | return HandleNullableListCount(x, y); 174 | if (x.Count != y.Count) 175 | return false; 176 | for (int i = 0; i < x.Count; i++) { 177 | if (!Equals(x[i], y[i])) 178 | return false; 179 | } 180 | return true; 181 | } 182 | 183 | bool Equals_NameList(IList? x, IList? y) { 184 | if (x == y) 185 | return true; 186 | if (x is null || y is null) 187 | return HandleNullableListCount(x, y); 188 | if (x.Count != y.Count) 189 | return false; 190 | for (int i = 0; i < x.Count; i++) { 191 | if (x[i] != y[i]) 192 | return false; 193 | } 194 | return true; 195 | } 196 | 197 | static int GetHashCode_NameList(IList? obj) { 198 | if (obj is null) 199 | return 0; 200 | int hash = 0; 201 | foreach (var name in obj) 202 | hash = (hash * -1521134295) + name.GetHashCode(); 203 | return hash; 204 | } 205 | 206 | bool HandleNullableListCount(IList? x, IList? y) { 207 | if (nullEqualsEmpty) { 208 | if (x is null) 209 | return y!.Count == 0; 210 | if (y is null) 211 | return x!.Count == 0; 212 | } 213 | return false; 214 | } 215 | 216 | #region Definition Comparer 217 | bool Equals_ComplexType(PortableComplexType? x, PortableComplexType? y) { 218 | if (x is PortableComplexType x1) 219 | return y is PortableComplexType y1 && Equals(x1, y1); 220 | else 221 | return y is null; 222 | } 223 | 224 | static bool Equals_ClassLayout(PortableClassLayout? x, PortableClassLayout? y) { 225 | if (x is PortableClassLayout x1) { 226 | if (y is not PortableClassLayout y1) 227 | return false; 228 | return x1.PackingSize == y1.PackingSize && x1.ClassSize == y1.ClassSize; 229 | } 230 | else 231 | return y is null; 232 | } 233 | 234 | static bool Equals_ImplMap(PortableImplMap? x, PortableImplMap? y) { 235 | if (x is PortableImplMap x1) { 236 | if (y is not PortableImplMap y1) 237 | return false; 238 | return x1.Name == y1.Name && x1.Module == y1.Module && x1.Attributes == y1.Attributes; 239 | } 240 | else 241 | return y is null; 242 | } 243 | 244 | bool Equals_MethodBody(PortableMethodBody? x, PortableMethodBody? y) { 245 | if (x is PortableMethodBody x1) { 246 | if (y is not PortableMethodBody y1) 247 | return false; 248 | if (!Equals_InstructionList(x1.Instructions, y1.Instructions)) 249 | return false; 250 | if (!Equals_ExceptionHandlerList(x1.ExceptionHandlers, y1.ExceptionHandlers)) 251 | return false; 252 | if (!Equals_ComplexTypeList(x1.Variables, y1.Variables)) 253 | return false; 254 | return true; 255 | } 256 | else 257 | return y is null; 258 | } 259 | 260 | static bool Equals_Constant(PortableConstant? x, PortableConstant? y) { 261 | if (x is PortableConstant x1) { 262 | if (y is not PortableConstant y1) 263 | return false; 264 | if (x1.Type != y1.Type) 265 | return false; 266 | if (x1.Value is object xv) 267 | return y1.Value is object yv && xv.Equals(yv); 268 | else 269 | return y1.Value is null; 270 | } 271 | else 272 | return y is null; 273 | } 274 | 275 | bool Equals_TokenList(IList? x, IList? y) { 276 | if (x == y) 277 | return true; 278 | if (x is null || y is null) 279 | return HandleNullableListCount(x, y); 280 | if (x.Count != y.Count) 281 | return false; 282 | for (int i = 0; i < x.Count; i++) { 283 | if (x[i] != y[i]) 284 | return false; 285 | } 286 | return true; 287 | } 288 | 289 | bool Equals_CustomAttributeList(IList? x, IList? y) { 290 | if (x == y) 291 | return true; 292 | if (x is null || y is null) 293 | return HandleNullableListCount(x, y); 294 | if (x.Count != y.Count) 295 | return false; 296 | for (int i = 0; i < x.Count; i++) { 297 | if (x[i].Constructor != y[i].Constructor) 298 | return false; 299 | if (!Equals_ByteArray(x[i].RawData, y[i].RawData)) 300 | return false; 301 | } 302 | return true; 303 | } 304 | 305 | bool Equals_GenericParamList(IList? x, IList? y) { 306 | if (x == y) 307 | return true; 308 | if (x is null || y is null) 309 | return HandleNullableListCount(x, y); 310 | if (x.Count != y.Count) 311 | return false; 312 | for (int i = 0; i < x.Count; i++) { 313 | if (x[i].Name != y[i].Name || x[i].Attributes != y[i].Attributes || x[i].Number != y[i].Number || !Equals_ComplexTypeList(x[i].Constraints, y[i].Constraints)) 314 | return false; 315 | } 316 | return true; 317 | } 318 | 319 | bool Equals_PropertyList(IList? x, IList? y) { 320 | if (x == y) 321 | return true; 322 | if (x is null || y is null) 323 | return HandleNullableListCount(x, y); 324 | if (x.Count != y.Count) 325 | return false; 326 | for (int i = 0; i < x.Count; i++) { 327 | if (x[i].Name != y[i].Name || !Equals(x[i].Signature, y[i].Signature) || x[i].Attributes != y[i].Attributes 328 | || x[i].GetMethod != y[i].GetMethod || x[i].SetMethod != y[i].SetMethod || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes)) 329 | return false; 330 | } 331 | return true; 332 | } 333 | 334 | bool Equals_EventList(IList? x, IList? y) { 335 | if (x == y) 336 | return true; 337 | if (x is null || y is null) 338 | return HandleNullableListCount(x, y); 339 | if (x.Count != y.Count) 340 | return false; 341 | for (int i = 0; i < x.Count; i++) { 342 | if (x[i].Name != y[i].Name || !Equals(x[i].Type, y[i].Type) || x[i].Attributes != y[i].Attributes || x[i].AddMethod != y[i].AddMethod 343 | || x[i].RemoveMethod != y[i].RemoveMethod || x[i].InvokeMethod != y[i].InvokeMethod || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes)) 344 | return false; 345 | } 346 | return true; 347 | } 348 | 349 | bool Equals_ParamList(IList? x, IList? y) { 350 | if (x == y) 351 | return true; 352 | if (x is null || y is null) 353 | return HandleNullableListCount(x, y); 354 | if (x.Count != y.Count) 355 | return false; 356 | for (int i = 0; i < x.Count; i++) { 357 | if (x[i].Name != y[i].Name || x[i].Sequence != y[i].Sequence || x[i].Attributes != y[i].Attributes 358 | || !Equals_CustomAttributeList(x[i].CustomAttributes, y[i].CustomAttributes) || !Equals_Constant(x[i].Constant, y[i].Constant)) 359 | return false; 360 | } 361 | return true; 362 | } 363 | 364 | bool Equals_ExceptionHandlerList(IList? x, IList? y) { 365 | if (x == y) 366 | return true; 367 | if (x is null || y is null) 368 | return HandleNullableListCount(x, y); 369 | if (x.Count != y.Count) 370 | return false; 371 | for (int i = 0; i < x.Count; i++) { 372 | if (x[i].TryStart != y[i].TryStart || x[i].TryEnd != y[i].TryEnd || x[i].FilterStart != y[i].FilterStart 373 | || x[i].HandlerStart != y[i].HandlerStart || x[i].HandlerType != y[i].HandlerType || !Equals_ComplexType(x[i].CatchType, y[i].CatchType)) 374 | return false; 375 | } 376 | return true; 377 | } 378 | 379 | bool Equals_InstructionList(IList? x, IList? y) { 380 | if (x == y) 381 | return true; 382 | if (x is null || y is null) 383 | return HandleNullableListCount(x, y); 384 | if (x.Count != y.Count) 385 | return false; 386 | for (int i = 0; i < x.Count; i++) { 387 | if (x[i].OpCode != y[i].OpCode) 388 | return false; 389 | switch (x[i].Operand) { 390 | case null: 391 | if (y[i].Operand is not null) 392 | return false; 393 | break; 394 | case int xi: 395 | if (y[i].Operand is not int yi || xi != yi) 396 | return false; 397 | break; 398 | case long xl: 399 | if (y[i].Operand is not long yl || xl != yl) 400 | return false; 401 | break; 402 | case float xf: 403 | if (y[i].Operand is not float yf || BitConverter.DoubleToInt64Bits(xf) != BitConverter.DoubleToInt64Bits(yf)) 404 | return false; 405 | break; 406 | case double xd: 407 | if (y[i].Operand is not double yd || BitConverter.DoubleToInt64Bits(xd) != BitConverter.DoubleToInt64Bits(yd)) 408 | return false; 409 | break; 410 | case string xs: 411 | if (y[i].Operand is not string ys || xs != ys) 412 | return false; 413 | break; 414 | case int[] xia: 415 | if (y[i].Operand is not int[] yia || !Equals_Int32Array(xia, yia)) 416 | return false; 417 | break; 418 | case PortableComplexType xt: 419 | if (y[i].Operand is not PortableComplexType yt || !Equals(xt, yt)) 420 | return false; 421 | break; 422 | default: 423 | throw new NotSupportedException(); 424 | } 425 | } 426 | return true; 427 | } 428 | 429 | bool Equals_Int32Array(int[]? x, int[]? y) { 430 | if (x == y) 431 | return true; 432 | if (x is null || y is null) 433 | return HandleNullableListCount(x, y); 434 | if (x.Length != y.Length) 435 | return false; 436 | for (int i = 0; i < x.Length; i++) { 437 | if (x[i] != y[i]) 438 | return false; 439 | } 440 | return true; 441 | } 442 | 443 | bool Equals_ByteArray(byte[]? x, byte[]? y) { 444 | if (x == y) 445 | return true; 446 | if (x is null || y is null) 447 | return HandleNullableListCount(x, y); 448 | if (x.Length != y.Length) 449 | return false; 450 | for (int i = 0; i < x.Length; i++) { 451 | if (x[i] != y[i]) 452 | return false; 453 | } 454 | return true; 455 | } 456 | #endregion 457 | } 458 | -------------------------------------------------------------------------------- /PortableMetadata/PortableMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System; 4 | using ElementType2 = MetadataSerialization.PortableComplexTypeFormatter.ElementType; 5 | 6 | namespace MetadataSerialization; 7 | 8 | /// 9 | /// Represents a portable method. 10 | /// 11 | /// The name of the method. 12 | /// The declaring type of the method. 13 | /// The signature of the method. 14 | public class PortableMethod(string name, PortableComplexType type, PortableComplexType signature) { 15 | /// 16 | /// Gets or sets the name of the method. 17 | /// 18 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 19 | 20 | /// 21 | /// Gets or sets the declaring type of the method. 22 | /// 23 | /// TypeDefOrRef 24 | public PortableComplexType Type { get; set; } = type; 25 | 26 | /// 27 | /// Gets or sets the signature of the method. 28 | /// 29 | /// MethodSig 30 | public PortableComplexType Signature { get; set; } = signature; 31 | 32 | /// 33 | /// Returns the name of the method. 34 | /// 35 | /// The name of the method. 36 | public override string ToString() { 37 | return Name; 38 | } 39 | } 40 | 41 | /// 42 | /// Represents a portable parameter. 43 | /// 44 | /// The name of the parameter. 45 | /// The sequence of the parameter. 46 | /// The attributes of the parameter. 47 | /// The constant value of the parameter. 48 | /// The custom attributes of the parameter. 49 | public struct PortableParameter(string name, int sequence, int attributes, PortableConstant? constant, IList? customAttributes) { 50 | /// 51 | /// Gets or sets the name of the parameter. 52 | /// 53 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 54 | 55 | /// 56 | /// Gets or sets the sequence of the parameter. 57 | /// 58 | public int Sequence { get; set; } = sequence; 59 | 60 | /// 61 | /// Gets or sets the attributes of the parameter. 62 | /// 63 | public int Attributes { get; set; } = attributes; 64 | 65 | /// 66 | /// Gets or sets the constant value of the parameter. 67 | /// 68 | public PortableConstant? Constant { get; set; } = constant; 69 | 70 | /// 71 | /// Gets or sets the custom attributes of the parameter. 72 | /// 73 | public IList? CustomAttributes { get; set; } = customAttributes; 74 | } 75 | 76 | /// 77 | /// Represents a portable instruction. 78 | /// 79 | /// The opcode of the instruction. 80 | /// The operand of the instruction. 81 | public struct PortableInstruction(string opCode, object? operand) { 82 | /// 83 | /// Gets or sets the opcode of the instruction. 84 | /// 85 | public string OpCode { get; set; } = opCode ?? throw new ArgumentNullException(nameof(opCode)); 86 | 87 | /// 88 | /// Gets or sets the operand of the instruction. 89 | /// 90 | /// 91 | /// The operand can be of type , , , , , , or . 92 | /// For , , , , , types, they should be stored as . 93 | /// 94 | public object? Operand { get; set; } = operand; 95 | 96 | /// 97 | [Obsolete("Reserved for deserialization.")] 98 | public long? PrimitiveValue { 99 | readonly get => PrimitivesHelper.ToSlot(Operand); 100 | set { 101 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand."); 102 | if (value is null) 103 | return; 104 | int type; 105 | switch (OpCode) { 106 | case "ldc.i8": 107 | type = (int)ElementType2.I8; 108 | break; 109 | case "ldc.r4": 110 | type = (int)ElementType2.R4; 111 | break; 112 | case "ldc.r8": 113 | type = (int)ElementType2.R8; 114 | break; 115 | default: 116 | type = (int)ElementType2.I4; 117 | break; 118 | } 119 | if (PrimitivesHelper.FromSlot(value, type) is object slot) 120 | Operand = slot; 121 | } 122 | } 123 | 124 | /// 125 | [Obsolete("Reserved for deserialization.")] 126 | public string? StringValue { 127 | readonly get => Operand as string; 128 | set { 129 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand."); 130 | if (OpCode == "ldstr" && value is string s) 131 | Operand = s; 132 | } 133 | } 134 | 135 | // Newtonsoft.Json's default behavior of deserializing List is appending to the existing list instead of replacing it. Use fixed-size array can avoid some potential issues. 136 | /// 137 | [Obsolete("Reserved for deserialization.")] 138 | public int[]? Int32ArrayValue { 139 | readonly get => Operand as int[]; 140 | set { 141 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand."); 142 | if (OpCode == "switch" && value is int[] v) 143 | Operand = v; 144 | } 145 | } 146 | 147 | /// 148 | [Obsolete("Reserved for deserialization.")] 149 | public PortableComplexType? TypeValue { 150 | readonly get => Operand is PortableComplexType type ? type : default(PortableComplexType?); 151 | set { 152 | Debug.Assert(OpCode is not null, "We assume OpCode is set before setting Operand."); 153 | if (value is PortableComplexType type) 154 | Operand = type; 155 | } 156 | } 157 | } 158 | 159 | /// 160 | /// Represents a portable method. 161 | /// 162 | /// The start index of the try block. 163 | /// The end index of the try block. 164 | /// The start index of the filter block. 165 | /// The start index of the handler block. 166 | /// The end index of the handler block. 167 | /// The catch type of the exception handler. 168 | /// The type of the exception handler. 169 | public struct PortableExceptionHandler(int tryStart, int tryEnd, int filterStart, int handlerStart, int handlerEnd, PortableComplexType? catchType, int handlerType) { 170 | /// 171 | /// Gets or sets the start index of the try block. 172 | /// 173 | public int TryStart { get; set; } = tryStart; 174 | 175 | /// 176 | /// Gets or sets the end index of the try block. 177 | /// 178 | public int TryEnd { get; set; } = tryEnd; 179 | 180 | /// 181 | /// Gets or sets the start index of the filter block. 182 | /// 183 | public int FilterStart { get; set; } = filterStart; 184 | 185 | /// 186 | /// Gets or sets the start index of the handler block. 187 | /// 188 | public int HandlerStart { get; set; } = handlerStart; 189 | 190 | /// 191 | /// Gets or sets the end index of the handler block. 192 | /// 193 | public int HandlerEnd { get; set; } = handlerEnd; 194 | 195 | /// 196 | /// Gets or sets the catch type of the exception handler. 197 | /// 198 | /// TypeDefOrRef 199 | public PortableComplexType? CatchType { get; set; } = catchType; 200 | 201 | /// 202 | /// Gets or sets the type of the exception handler. 203 | /// 204 | public int HandlerType { get; set; } = handlerType; 205 | } 206 | 207 | /// 208 | /// Represents a portable method body. 209 | /// 210 | /// The list of instructions in the method body. 211 | /// The list of exception handlers in the method body. 212 | /// The list of variables in the method body. 213 | /// The max stack value of the method body. 214 | /// The init locals flag. 215 | public struct PortableMethodBody(IList instructions, IList exceptionHandlers, IList variables, int maxStack, bool initLocals) { 216 | /// 217 | /// Gets or sets the list of instructions in the method body. 218 | /// 219 | public IList Instructions { get; set; } = instructions; 220 | 221 | /// 222 | /// Gets or sets the list of exception handlers in the method body. 223 | /// 224 | public IList ExceptionHandlers { get; set; } = exceptionHandlers; 225 | 226 | /// 227 | /// Gets or sets the list of variables in the method body. 228 | /// 229 | public IList Variables { get; set; } = variables; 230 | 231 | /// 232 | /// Gets or sets the max stack value of the method body. 233 | /// 234 | public int MaxStack { get; set; } = maxStack; 235 | 236 | /// 237 | /// Gets or sets the init locals flag. 238 | /// 239 | public bool InitLocals { get; set; } = initLocals; 240 | } 241 | 242 | /// 243 | /// Represents a portable PInvoke mapping. 244 | /// 245 | /// The name of the PInvoke method. 246 | /// The module name of the PInvoke method. 247 | /// The attributes of the PInvoke method. 248 | public struct PortableImplMap(string name, string module, int attributes) { 249 | /// 250 | /// Gets or sets the name of the PInvoke method. 251 | /// 252 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 253 | 254 | /// 255 | /// Gets or sets the module name of the PInvoke method. 256 | /// 257 | public string Module { get; set; } = module ?? throw new ArgumentNullException(nameof(module)); 258 | 259 | /// 260 | /// Gets or sets the attributes of the PInvoke method. 261 | /// 262 | public int Attributes { get; set; } = attributes; 263 | 264 | /// 265 | /// Returns the name of the PInvoke method. 266 | /// 267 | /// The name of the PInvoke method. 268 | public override readonly string ToString() { 269 | return Name; 270 | } 271 | } 272 | 273 | /// 274 | /// Represents a portable method definition. 275 | /// 276 | /// The name of the method. 277 | /// The declaring type of the method. 278 | /// The signature of the method. 279 | /// The attributes of the method. 280 | /// The implementation attributes of the method. 281 | /// The parameters of the method. 282 | /// The method body. 283 | /// The overridden methods. 284 | /// The PInvoke mapping information. 285 | /// The generic parameters of the method. 286 | /// The custom attributes of the method. 287 | public class PortableMethodDef(string name, PortableComplexType type, PortableComplexType signature, int attributes, int implAttributes, 288 | IList parameters, PortableMethodBody? body, IList? overrides, PortableImplMap? implMap, 289 | IList? genericParameters, IList? customAttributes) 290 | : PortableMethod(name, type, signature) { 291 | /// 292 | /// Gets or sets the attributes of the method. 293 | /// 294 | public int Attributes { get; set; } = attributes; 295 | 296 | /// 297 | /// Gets or sets the implementation attributes of the method. 298 | /// 299 | public int ImplAttributes { get; set; } = implAttributes; 300 | 301 | /// 302 | /// Gets or sets the parameters of the method. 303 | /// 304 | public IList Parameters { get; set; } = parameters; 305 | 306 | /// 307 | /// Gets or sets the method body. 308 | /// 309 | public PortableMethodBody? Body { get; set; } = body; 310 | 311 | /// 312 | /// Gets or sets the overridden methods. 313 | /// 314 | /// MethodDefOrRef 315 | public IList? Overrides { get; set; } = overrides; 316 | 317 | /// 318 | /// Gets or sets the PInvoke mapping information. 319 | /// 320 | public PortableImplMap? ImplMap { get; set; } = implMap; 321 | 322 | /// 323 | /// Gets or sets the generic parameters of the method. 324 | /// 325 | public IList? GenericParameters { get; set; } = genericParameters; 326 | 327 | /// 328 | /// Gets or sets the custom attributes of the method. 329 | /// 330 | public IList? CustomAttributes { get; set; } = customAttributes; 331 | } 332 | -------------------------------------------------------------------------------- /PortableMetadata/PortableType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MetadataSerialization; 5 | 6 | /// 7 | /// Represents a portable type. 8 | /// 9 | /// The name of the type. 10 | /// The namespace of the type. 11 | /// The assembly of the type. 12 | /// The enclosing names of the type. 13 | public class PortableType(string name, string @namespace, string? assembly, IList? enclosingNames) { 14 | /// 15 | /// Gets or sets the name of the type. 16 | /// 17 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 18 | 19 | /// 20 | /// Gets or sets the namespace of the type. 21 | /// 22 | public string Namespace { get; set; } = @namespace ?? throw new ArgumentNullException(nameof(@namespace)); 23 | 24 | /// 25 | /// Gets or sets the assembly of the type. 26 | /// 27 | public string? Assembly { get; set; } = assembly; 28 | 29 | /// 30 | /// Gets or sets the enclosing names of the type. 31 | /// 32 | public IList? EnclosingNames { get; set; } = enclosingNames; 33 | 34 | /// 35 | /// Returns the name of the type. 36 | /// 37 | /// The name of the type. 38 | public override string ToString() { 39 | return Name; 40 | } 41 | } 42 | 43 | /// 44 | /// Represents the layout of a type. 45 | /// 46 | /// The packing size of the type. 47 | /// The size of the type. 48 | public struct PortableClassLayout(int packingSize, int classSize) { 49 | /// 50 | /// Gets or sets the packing size of the type. 51 | /// 52 | public int PackingSize { get; set; } = packingSize; 53 | 54 | /// 55 | /// Gets or sets the size of the type. 56 | /// 57 | public int ClassSize { get; set; } = classSize; 58 | } 59 | 60 | /// 61 | /// Represents a portable property. 62 | /// 63 | /// The name of the property. 64 | /// The signature of the property. 65 | /// The attributes of the property. 66 | /// The get method of the property. 67 | /// The set method of the property. 68 | /// The custom attributes of the property. 69 | public struct PortableProperty(string name, PortableComplexType signature, int attributes, PortableToken? getMethod, PortableToken? setMethod, IList? customAttributes) { 70 | /// 71 | /// Gets or sets the name of the property. 72 | /// 73 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 74 | 75 | /// 76 | /// Gets or sets the signature of the property. 77 | /// 78 | /// PropertySig 79 | public PortableComplexType Signature { get; set; } = signature; 80 | 81 | /// 82 | /// Gets or sets the attributes of the property. 83 | /// 84 | public int Attributes { get; set; } = attributes; 85 | 86 | /// 87 | /// Gets or sets the get method of the property. 88 | /// 89 | public PortableToken? GetMethod { get; set; } = getMethod; 90 | 91 | /// 92 | /// Gets or sets the set method of the property. 93 | /// 94 | public PortableToken? SetMethod { get; set; } = setMethod; 95 | 96 | /// 97 | /// Gets or sets the custom attributes of the property. 98 | /// 99 | public IList? CustomAttributes { get; set; } = customAttributes; 100 | 101 | /// 102 | /// Returns the name of the property. 103 | /// 104 | /// The name of the property. 105 | public override readonly string ToString() { 106 | return Name; 107 | } 108 | } 109 | 110 | /// 111 | /// Represents a portable event. 112 | /// 113 | /// The name of the event. 114 | /// The type of the event. 115 | /// The attributes of the event. 116 | /// The add method of the event. 117 | /// The remove method of the event. 118 | /// The invoke method of the event. 119 | /// The custom attributes of the event. 120 | public struct PortableEvent(string name, PortableComplexType type, int attributes, PortableToken? addMethod, PortableToken? removeMethod, PortableToken? invokeMethod, IList? customAttributes) { 121 | /// 122 | /// Gets or sets the name of the event. 123 | /// 124 | public string Name { get; set; } = name ?? throw new ArgumentNullException(nameof(name)); 125 | 126 | /// 127 | /// Gets or sets the type of the event. 128 | /// 129 | /// TypeDefOrRef 130 | public PortableComplexType Type { get; set; } = type; 131 | 132 | /// 133 | /// Gets or sets the attributes of the event. 134 | /// 135 | public int Attributes { get; set; } = attributes; 136 | 137 | /// 138 | /// Gets or sets the add method of the event. 139 | /// 140 | public PortableToken? AddMethod { get; set; } = addMethod; 141 | 142 | /// 143 | /// Gets or sets the remove method of the event. 144 | /// 145 | public PortableToken? RemoveMethod { get; set; } = removeMethod; 146 | 147 | /// 148 | /// Gets or sets the invoke method of the event. 149 | /// 150 | public PortableToken? InvokeMethod { get; set; } = invokeMethod; 151 | 152 | /// 153 | /// Gets or sets the custom attributes of the event. 154 | /// 155 | public IList? CustomAttributes { get; set; } = customAttributes; 156 | 157 | /// 158 | /// Returns the name of the event. 159 | /// 160 | /// The name of the event. 161 | public override readonly string ToString() { 162 | return Name; 163 | } 164 | } 165 | 166 | /// 167 | /// Represents a portable type definition. 168 | /// 169 | /// The name of the type. 170 | /// The namespace of the type. 171 | /// The assembly of the type. 172 | /// The enclosing names of the type. 173 | /// The attributes of the type. 174 | /// The base type of the type. 175 | /// The interfaces implemented by the type. 176 | /// The class layout of the type. 177 | /// The generic parameters of the type. 178 | /// The custom attributes of the type. 179 | public sealed class PortableTypeDef(string name, string @namespace, string? assembly, IList? enclosingNames, int attributes, 180 | PortableComplexType? baseType, IList? interfaces, PortableClassLayout? classLayout, 181 | IList? genericParameters, IList? customAttributes) 182 | : PortableType(name, @namespace, assembly, enclosingNames) { 183 | /// 184 | /// Gets or sets the attributes of the type. 185 | /// 186 | public int Attributes { get; set; } = attributes; 187 | 188 | /// 189 | /// Gets or sets the base type of the type. 190 | /// 191 | /// TypeDefOrRef 192 | public PortableComplexType? BaseType { get; set; } = baseType; 193 | 194 | /// 195 | /// Gets or sets the interfaces implemented by the type. 196 | /// 197 | /// TypeDefOrRef 198 | public IList? Interfaces { get; set; } = interfaces; 199 | 200 | /// 201 | /// Gets or sets the class layout of the type. 202 | /// 203 | public PortableClassLayout? ClassLayout { get; set; } = classLayout; 204 | 205 | /// 206 | /// Gets or sets the generic parameters of the type. 207 | /// 208 | public IList? GenericParameters { get; set; } = genericParameters; 209 | 210 | /// 211 | /// Gets or sets the custom attributes of the type. 212 | /// 213 | public IList? CustomAttributes { get; set; } = customAttributes; 214 | 215 | /// 216 | /// Gets or sets the nested types of the type. 217 | /// 218 | public IList? NestedTypes { get; set; } 219 | 220 | /// 221 | /// Gets or sets the fields of the type. 222 | /// 223 | public IList? Fields { get; set; } 224 | 225 | /// 226 | /// Gets or sets the methods of the type. 227 | /// 228 | public IList? Methods { get; set; } 229 | 230 | /// 231 | /// Gets or sets the properties of the type. 232 | /// 233 | public IList? Properties { get; set; } 234 | 235 | /// 236 | /// Gets or sets the events of the type. 237 | /// 238 | public IList? Events { get; set; } 239 | } 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PortableMetadata 2 | 3 | .NET metadata serialization library. Defines a human-readable and serializable metadata data structure. The PortableMetadata class is the center of the entire library. 4 | 5 | ## Features 6 | 7 | 1. Support saving the entire assembly. 8 | 1. Support saving only a single or specific types/fields/methods. 9 | 1. PortableMetadata is serialization friendly. It can be serialized directly to json by Json.NET or System.Text.Json and does not require any custom converters. Using the custom converts can achieve better display results, and the relevant code is in the samples. 10 | 1. PortableMetadata only requires a binary size of around 50kb and does not require any external dependencies. Compatible with .NET Framework 2.0 to .NET 8.0+. 11 | 12 | ## Scenarios 13 | 14 | 1. Dump part of .NET metadata to view. 15 | 1. Transfer processed .NET metadata between processes. 16 | 1. Exchange data directly between different .NET metadata libraries. 17 | 1. Create a plaintext patch and apply it to an existing assembly. 18 | 1. And more... 19 | 20 | ## Limits 21 | 22 | 1. Do NOT support the varargs method. 23 | 1. Do NOT support the multiple module assemblies. 24 | 25 | ## Samples 26 | 27 | ### Basics 28 | 29 | Currently, the built-in reader/writer that uses dnlib are provided. Assume the corresponding namespaces are imported. 30 | 31 | ```cs 32 | using dnlib.DotNet; 33 | using dnlib.DotNet.MD; 34 | using MetadataSerialization; 35 | using MetadataSerialization.Dnlib; 36 | ``` 37 | 38 | Read the PortableMetadata from the ModuleDef in dnlib: 39 | 40 | ```cs 41 | // 1. Load the module using dnlib 42 | using ModuleDefMD module = ModuleDefMD.Load(typeof(YourType).Module); 43 | 44 | // 2. Create the portable metadata 45 | var reader = new PortableMetadataReader(module); 46 | reader.AddType(module.FindNormalThrow("YourType"), PortableMetadataLevel.DefinitionWithChildren); 47 | 48 | // 3. Get the PortableMetadata instance 49 | PortableMetadata metadata = reader.Metadata; 50 | ``` 51 | 52 | Write the data from the PortableMetadata to the ModuleDef: 53 | 54 | ```cs 55 | // 1. Specify the data source and module to write to 56 | ModuleDef module = ...; 57 | PortableMetadata metadata = ...; 58 | PortableType type = metadata.Types.Values.First(t => ...); 59 | 60 | // 2. Write the metadata from PortableMetadata to ModuleDef 61 | var writer = new PortableMetadataWriter(module, metadata); 62 | writer.AddType(type, PortableMetadataLevel.Definition.DefinitionWithChildren); 63 | ``` 64 | 65 | ### ExportOneMethod 66 | 67 | This example demonstrates how to export a method. 68 | 69 | Code is available in 'PortableMetadata.Samples\Sample_ExportOneMethod.cs'. 70 | 71 | ```cs 72 | static class Sample_ExportOneMethod { 73 | public static void DemoMethod() { 74 | Console.WriteLine("Hello World!"); 75 | } 76 | 77 | public static void Run() { 78 | // 1. Load the module using dnlib 79 | using var module = ModuleDefMD.Load(typeof(Sample_ExportOneMethod).Module); 80 | 81 | // 2. Create the portable metadata 82 | var reader = new PortableMetadataReader(module); 83 | reader.AddMethod(module.FindNormalThrow("Sample_ExportOneMethod").FindMethod("DemoMethod"), PortableMetadataLevel.Definition); 84 | var metadata = reader.Metadata; 85 | 86 | // 3. Export the metadata (without any converters) 87 | var json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); 88 | // Console.WriteLine(json); 89 | 90 | // 4. Export the metadata (with converters) 91 | var options = new JsonSerializerOptions { 92 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 93 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 94 | WriteIndented = true 95 | }; 96 | options.Converters.Add(new STJPortableTokenConverter()); 97 | options.Converters.Add(new STJPortableComplexTypeConverter()); 98 | json = JsonSerializer.Serialize(new PortableMetadataFacade(metadata), options); 99 | Console.WriteLine(json); 100 | } 101 | } 102 | ``` 103 | 104 | Output: 105 | 106 | ```json 107 | { 108 | "Options": 14, 109 | "Types": { 110 | "References": { 111 | "0": { 112 | "Name": "Sample_ExportOneMethod", 113 | "Namespace": "" 114 | }, 115 | "1": { 116 | "Name": "Console", 117 | "Namespace": "System", 118 | "Assembly": "System.Console, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 119 | } 120 | }, 121 | "Definitions": {}, 122 | "Orders": [] 123 | }, 124 | "Fields": { 125 | "References": {}, 126 | "Definitions": {}, 127 | "Orders": [] 128 | }, 129 | "Methods": { 130 | "References": { 131 | "1": { 132 | "Name": "WriteLine", 133 | "Type": "1", 134 | "Signature": "Default(Int32(0),Int32(1),Void,String)" 135 | } 136 | }, 137 | "Definitions": { 138 | "0": { 139 | "Attributes": 150, 140 | "ImplAttributes": 0, 141 | "Parameters": [], 142 | "Body": { 143 | "Instructions": [ 144 | { 145 | "OpCode": "nop" 146 | }, 147 | { 148 | "OpCode": "ldstr", 149 | "StringValue": "Hello World!" 150 | }, 151 | { 152 | "OpCode": "call", 153 | "TypeValue": "InlineMethod(1)" 154 | }, 155 | { 156 | "OpCode": "nop" 157 | }, 158 | { 159 | "OpCode": "ret" 160 | } 161 | ], 162 | "ExceptionHandlers": [], 163 | "Variables": [] 164 | }, 165 | "Name": "DemoMethod", 166 | "Type": "0", 167 | "Signature": "Default(Int32(0),Int32(0),Void)" 168 | } 169 | }, 170 | "Orders": [] 171 | } 172 | } 173 | ``` 174 | 175 | ### ImportOneMethod 176 | 177 | This example demonstrates how to import a method. 178 | 179 | Code is available in 'PortableMetadata.Samples\Sample_ImportOneMethod.cs'. 180 | 181 | ```cs 182 | static class Sample_ImportOneMethod { 183 | public static void DemoMethod() { 184 | //var list = new List { 185 | // "Hello", 186 | // "World" 187 | //}; 188 | //foreach (var item in list) 189 | // Console.WriteLine(item); 190 | throw new NotImplementedException(); 191 | } 192 | 193 | public static void Run() { 194 | // 1. Load the module using dnlib 195 | using var module = ModuleDefMD.Load(typeof(Sample_ImportOneMethod).Module); 196 | 197 | // 2. Deserialize the metadata 198 | var options = new JsonSerializerOptions { 199 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 200 | TypeInfoResolver = new DefaultJsonTypeInfoResolver().WithAddedModifier(STJPortableMetadataObjectPropertyRemover.RemoveObjectProperties), 201 | WriteIndented = true 202 | }; 203 | options.Converters.Add(new STJPortableTokenConverter()); 204 | options.Converters.Add(new STJPortableComplexTypeConverter()); 205 | var metadata = JsonSerializer.Deserialize(DemoMethodJson, options)!.ToMetadata(); 206 | var demoMethod = metadata.Methods[0]; 207 | Debug.Assert(demoMethod is PortableMethodDef); 208 | 209 | // 3. Import the method 210 | var writer = new PortableMetadataWriter(module, metadata); 211 | writer.AddMethod(demoMethod, PortableMetadataLevel.Definition); 212 | var path = Path.GetFullPath("Sample_ImportOneMethod.dll"); 213 | module.Assembly.Name = module.Name = Guid.NewGuid().ToString(); 214 | module.Write(path); 215 | Assembly.LoadFrom(path).GetType("Sample_ImportOneMethod")!.GetMethod("DemoMethod")!.Invoke(null, null); 216 | } 217 | 218 | const string DemoMethodJson = 219 | """ 220 | Omitted here, please go to the corresponding file to view the complete content 221 | """; 222 | } 223 | ``` 224 | 225 | ### Others 226 | 227 | See PortableMetadata.Samples\PortableMetadata.Samples.csproj. 228 | --------------------------------------------------------------------------------