diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..0ebd8e34
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,246 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = true:warning
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true:warning
+csharp_style_prefer_pattern_matching = true:warning
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_local_over_anonymous_function = true:warning
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+csharp_style_unused_value_expression_statement_preference = discard_variable:warning
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:warning
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = true:warning
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d5fe15c0..205d7910 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,6 +12,8 @@ defaults:
run:
shell: pwsh
+permissions: read-all
+
jobs:
build:
runs-on: windows-latest
@@ -33,20 +35,20 @@ jobs:
uses: actions/setup-dotnet@v1
with:
# As usual, obtained from: https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/5.0/releases.json
- dotnet-version: "3.1.404" # since we now use this
+ dotnet-version: "6.0.100" # since we now use this
+ - name: Log in to package source
+ shell: pwsh
+ run: |
+ dotnet nuget add source --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} `
+ --store-password-in-clear-text --name github "https://nuget.pkg.github.com/Atlas-Rhythm/index.json"
- name: Clear Nuget Cache
run: dotnet nuget locals all --clear
- name: Restore
run: msbuild -t:Restore -m
- name: Build
run: msbuild -t:Build -m
- - name: Upload net461
- uses: actions/upload-artifact@v2
- with:
- name: BSIPA-net461-${{ env.Platform }}
- path: BSIPA-Meta/bin/${{ env.Platform }}/${{ env.Configuration }}/net461/
- - name: Upload net35
+ - name: Upload net472
uses: actions/upload-artifact@v2
with:
- name: BSIPA-net35-${{ env.Platform }}
- path: BSIPA-Meta/bin/${{ env.Platform }}/${{ env.Configuration }}/net35/
+ name: BSIPA-net472-${{ env.Platform }}
+ path: BSIPA-Meta/bin/${{ env.Platform }}/${{ env.Configuration }}/net472/
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 868aaeab..0d60315f 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -30,7 +30,12 @@ jobs:
uses: actions/setup-dotnet@v1
with:
# As usual, obtained from: https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/5.0/releases.json
- dotnet-version: "3.1.404" # since we now use this
+ dotnet-version: "6.0.100" # since we now use this
+ - name: Log in to package source
+ shell: pwsh
+ run: |
+ dotnet nuget add source --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} `
+ --store-password-in-clear-text --name github "https://nuget.pkg.github.com/Atlas-Rhythm/index.json"
- name: Clear Nuget Cache
run: dotnet nuget locals all --clear
- name: Nuget Restore
diff --git a/.github/workflows/tag_docs.yml b/.github/workflows/tag_docs.yml
index c6031945..743a0e4c 100644
--- a/.github/workflows/tag_docs.yml
+++ b/.github/workflows/tag_docs.yml
@@ -29,7 +29,12 @@ jobs:
uses: actions/setup-dotnet@v1
with:
# As usual, obtained from: https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/5.0/releases.json
- dotnet-version: "3.1.404" # since we now use this
+ dotnet-version: "6.0.100" # since we now use this
+ - name: Log in to package source
+ shell: pwsh
+ run: |
+ dotnet nuget add source --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} `
+ --store-password-in-clear-text --name github "https://nuget.pkg.github.com/Atlas-Rhythm/index.json"
- name: Clear Nuget Cache
run: dotnet nuget locals all --clear
- name: Nuget Restore
@@ -37,7 +42,7 @@ jobs:
- name: Install DocFX
uses: crazy-max/ghaction-chocolatey@v1
with:
- args: install docfx --version 2.48 -y
+ args: install docfx -y
- name: Checkout current pages
uses: actions/checkout@v2
with:
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..5408330e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,12 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Attach to BSIPA",
+ "type": "mono",
+ "request": "attach",
+ "address": "localhost",
+ "port": 10000
+ }
+ ]
+}
\ No newline at end of file
diff --git a/BSIPA-Meta/BSIPA-Meta.csproj b/BSIPA-Meta/BSIPA-Meta.csproj
index cbdb384e..794a1291 100644
--- a/BSIPA-Meta/BSIPA-Meta.csproj
+++ b/BSIPA-Meta/BSIPA-Meta.csproj
@@ -1,7 +1,7 @@
- net35;net461
+ net472
x86;x64
Debug;Release;Verbose;Verbose_Release
@@ -59,9 +59,11 @@
+
+
@@ -69,7 +71,7 @@
-
+
@@ -87,3 +89,4 @@
+
diff --git a/BSIPA.sln b/BSIPA.sln
index f2a38632..3e223942 100644
--- a/BSIPA.sln
+++ b/BSIPA.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28729.10
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPA", "IPA\IPA.csproj", "{14092533-98BB-40A4-9AFC-27BB75672A70}"
ProjectSection(ProjectDependencies) = postProject
@@ -18,13 +18,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{C79C2C3A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4D6639A2-BD39-4F9B-AF7F-8E5F3B88243D}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
Common.props = Common.props
Common.targets = Common.targets
.github\workflows\docs.yml = .github\workflows\docs.yml
README.md = README.md
.github\release_draft.yml = .github\release_draft.yml
- System.Diagnostics.CodeAnalysis.cs = System.Diagnostics.CodeAnalysis.cs
.github\workflows\tag_docs.yml = .github\workflows\tag_docs.yml
EndProjectSection
EndProject
@@ -50,180 +50,144 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPA.Loader", "IPA.Loader\IP
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net3-Proxy", "Net3-Proxy\Net3-Proxy.csproj", "{0DEDB099-9A26-4069-A4C1-A76CEB16283B}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemVer", "SemVer\SemVer.csproj", "{B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
- Verbose_Release|Any CPU = Verbose_Release|Any CPU
Verbose_Release|x64 = Verbose_Release|x64
Verbose_Release|x86 = Verbose_Release|x86
- Verbose|Any CPU = Verbose|Any CPU
Verbose|x64 = Verbose|x64
Verbose|x86 = Verbose|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|x64.ActiveCfg = Debug|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|x64.Build.0 = Debug|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|x86.ActiveCfg = Debug|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Debug|x86.Build.0 = Debug|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Release|Any CPU.Build.0 = Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Release|x64.ActiveCfg = Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Release|x64.Build.0 = Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Release|x86.ActiveCfg = Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Release|x86.Build.0 = Release|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|Any CPU.Build.0 = Verbose_Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|x64.ActiveCfg = Verbose_Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|x64.Build.0 = Verbose_Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose_Release|x86.Build.0 = Verbose_Release|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|Any CPU.ActiveCfg = Verbose|Any CPU
- {14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|Any CPU.Build.0 = Verbose|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x64.ActiveCfg = Verbose|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x64.Build.0 = Verbose|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.ActiveCfg = Verbose|Any CPU
{14092533-98BB-40A4-9AFC-27BB75672A70}.Verbose|x86.Build.0 = Verbose|Any CPU
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|Any CPU.ActiveCfg = Verbose_Release|Win32
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|Any CPU.Build.0 = Verbose_Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.ActiveCfg = Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x64.Build.0 = Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.ActiveCfg = Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Debug|x86.Build.0 = Release|Win32
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Release|Any CPU.ActiveCfg = Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.ActiveCfg = Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x64.Build.0 = Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.ActiveCfg = Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Release|x86.Build.0 = Release|Win32
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.ActiveCfg = Verbose_Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x64.Build.0 = Verbose_Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.ActiveCfg = Verbose_Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose_Release|x86.Build.0 = Verbose_Release|Win32
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|Any CPU.ActiveCfg = Verbose_Release|Win32
- {88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|Any CPU.Build.0 = Verbose_Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.ActiveCfg = Verbose_Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x64.Build.0 = Verbose_Release|x64
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.ActiveCfg = Verbose_Release|Win32
{88609E16-731F-46C9-8139-6B1A7A83240D}.Verbose|x86.Build.0 = Verbose_Release|Win32
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|x64.ActiveCfg = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|x64.Build.0 = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|x86.ActiveCfg = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Debug|x86.Build.0 = Debug|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Release|Any CPU.Build.0 = Release|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Release|x64.ActiveCfg = Release|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Release|x86.ActiveCfg = Release|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose_Release|Any CPU.ActiveCfg = Release|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose_Release|Any CPU.Build.0 = Release|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
- {5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|Any CPU.Build.0 = Release|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|x64.ActiveCfg = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|x64.Build.0 = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|x86.ActiveCfg = Debug|Any CPU
{5F33B310-DC8D-4C0D-877E-BAC3908DE10F}.Verbose|x86.Build.0 = Debug|Any CPU
- {880A3560-82CD-4836-996B-11BEFE6B44DB}.Debug|Any CPU.ActiveCfg = Debug|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Debug|x64.ActiveCfg = Debug|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Debug|x64.Build.0 = Debug|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Debug|x86.ActiveCfg = Debug|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Debug|x86.Build.0 = Debug|x86
- {880A3560-82CD-4836-996B-11BEFE6B44DB}.Release|Any CPU.ActiveCfg = Release|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Release|x64.ActiveCfg = Release|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Release|x64.Build.0 = Release|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Release|x86.ActiveCfg = Release|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Release|x86.Build.0 = Release|x86
- {880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose_Release|Any CPU.ActiveCfg = Verbose_Release|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose_Release|x64.ActiveCfg = Verbose_Release|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose_Release|x64.Build.0 = Verbose_Release|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose_Release|x86.ActiveCfg = Verbose_Release|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose_Release|x86.Build.0 = Verbose_Release|x86
- {880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose|Any CPU.ActiveCfg = Verbose|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose|x64.ActiveCfg = Verbose|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose|x64.Build.0 = Verbose|x64
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose|x86.ActiveCfg = Verbose|x86
{880A3560-82CD-4836-996B-11BEFE6B44DB}.Verbose|x86.Build.0 = Verbose|x86
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|x64.ActiveCfg = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|x64.Build.0 = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|x86.ActiveCfg = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Debug|x86.Build.0 = Debug|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|Any CPU.Build.0 = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|x64.ActiveCfg = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|x64.Build.0 = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|x86.ActiveCfg = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Release|x86.Build.0 = Release|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|Any CPU.ActiveCfg = Release|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|Any CPU.Build.0 = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|x64.Build.0 = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose_Release|x86.Build.0 = Release|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
- {10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|Any CPU.Build.0 = Release|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|x64.ActiveCfg = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|x64.Build.0 = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|x86.ActiveCfg = Debug|Any CPU
{10F0057C-6C1E-41AA-A4DE-2F9D2EABE55C}.Verbose|x86.Build.0 = Debug|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|x64.ActiveCfg = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|x64.Build.0 = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|x86.ActiveCfg = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Debug|x86.Build.0 = Debug|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|Any CPU.Build.0 = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|x64.ActiveCfg = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|x64.Build.0 = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|x86.ActiveCfg = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Release|x86.Build.0 = Release|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|Any CPU.ActiveCfg = Release|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|Any CPU.Build.0 = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|x64.Build.0 = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose_Release|x86.Build.0 = Release|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
- {BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|Any CPU.Build.0 = Release|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|x64.ActiveCfg = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|x64.Build.0 = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|x86.ActiveCfg = Debug|Any CPU
{BBBA5CAD-B40E-4565-AE96-E8EC468DB54B}.Verbose|x86.Build.0 = Debug|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|x64.ActiveCfg = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|x64.Build.0 = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|x86.ActiveCfg = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Debug|x86.Build.0 = Debug|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|Any CPU.Build.0 = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|x64.ActiveCfg = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|x64.Build.0 = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|x86.ActiveCfg = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Release|x86.Build.0 = Release|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|Any CPU.ActiveCfg = Release|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|Any CPU.Build.0 = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|x64.Build.0 = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose_Release|x86.Build.0 = Release|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
- {0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|Any CPU.Build.0 = Release|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|x64.ActiveCfg = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|x64.Build.0 = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|x86.ActiveCfg = Debug|Any CPU
{0DEDB099-9A26-4069-A4C1-A76CEB16283B}.Verbose|x86.Build.0 = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Debug|x64.Build.0 = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Debug|x86.Build.0 = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Release|x64.ActiveCfg = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Release|x64.Build.0 = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Release|x86.ActiveCfg = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Release|x86.Build.0 = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose_Release|x64.ActiveCfg = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose_Release|x64.Build.0 = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose_Release|x86.ActiveCfg = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose_Release|x86.Build.0 = Release|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose|x64.ActiveCfg = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose|x64.Build.0 = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose|x86.ActiveCfg = Debug|Any CPU
+ {B25EEC48-A5D0-4A63-BA73-5DD43F7F592A}.Verbose|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BuildTools b/BuildTools
index 41c3a12d..b04769a3 160000
--- a/BuildTools
+++ b/BuildTools
@@ -1 +1 @@
-Subproject commit 41c3a12d56de96a3495893d1fea4a485a98c67af
+Subproject commit b04769a3aebdd111b81f5a59d438907310e83207
diff --git a/Common.props b/Common.props
index c3f703e4..f33175a4 100644
--- a/Common.props
+++ b/Common.props
@@ -11,5 +11,17 @@
AllEnabledByDefault
latest
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
\ No newline at end of file
diff --git a/Common.targets b/Common.targets
index 56750be5..f6c26ae1 100644
--- a/Common.targets
+++ b/Common.targets
@@ -1,8 +1,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/Doorstop/Proxy/main.c b/Doorstop/Proxy/main.c
index 5d3d51f9..c925a1a3 100644
--- a/Doorstop/Proxy/main.c
+++ b/Doorstop/Proxy/main.c
@@ -111,6 +111,13 @@ void unhandledException(void* exc, void* data)
// We use this since it will always be called once to initialize Mono's JIT
void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
{
+ const BOOL debugger_already_initialized = mono_debug_enabled();
+
+ if(debugger_already_initialized)
+ {
+ LOG("Debugger was already initialized\n");
+ }
+
// Call the original mono_jit_init_version to initialize the Unity Root Domain
if (debug) {
char* opts[1];
@@ -118,14 +125,14 @@ void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_ve
ownMonoJitParseOptions(0, opts);
}
#ifdef WIN32
- if (debug_info) {
+ if (debug_info && !debugger_already_initialized) {
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
}
#endif
void *domain = mono_jit_init_version(root_domain_name, runtime_version);
- if (debug_info) {
+ if (debug_info && !debugger_already_initialized) {
#ifdef WIN64
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
#endif
@@ -194,8 +201,10 @@ void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_ve
wchar_t* dll_path_w; // self path
size_t dll_path_len = get_module_path((HINSTANCE)&__ImageBase, &dll_path_w, NULL, 0);
- char* self_dll_path = memalloc(dll_path_len + 1);
- WideCharToMultiByte(CP_UTF8, 0, dll_path_w, -1, self_dll_path, dll_path_len + 1, NULL, NULL);
+ size_t multibyte_path_len = WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, NULL, 0, NULL, NULL);
+ char* self_dll_path = memalloc(multibyte_path_len + 1);
+ WideCharToMultiByte(CP_UTF8, 0, dll_path_w, dll_path_len, self_dll_path, multibyte_path_len + 1, NULL, NULL);
+ self_dll_path[multibyte_path_len] = 0;
mono_dllmap_insert(NULL, "i:bsipa-doorstop", NULL, self_dll_path, NULL); // remap `bsipa-doorstop` to this assembly
diff --git a/Doorstop/Proxy/mono.h b/Doorstop/Proxy/mono.h
index 6eb41f41..f3ed2c4a 100644
--- a/Doorstop/Proxy/mono.h
+++ b/Doorstop/Proxy/mono.h
@@ -40,6 +40,7 @@ typedef enum {
void (*mono_jit_parse_options)(int argc, char * argv[]);
void (*mono_debug_init)(MonoDebugFormat format);
+BOOL (*mono_debug_enabled)(void);
void (*mono_debug_domain_create)(void*);
void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version);
@@ -90,6 +91,7 @@ inline void loadMonoFunctions(HMODULE monoLib)
GET_MONO_PROC(mono_assembly_get_image);
GET_MONO_PROC(mono_runtime_invoke);
GET_MONO_PROC(mono_debug_init);
+ GET_MONO_PROC(mono_debug_enabled);
GET_MONO_PROC(mono_jit_init_version);
GET_MONO_PROC(mono_jit_parse_options);
GET_MONO_PROC(mono_method_desc_new);
diff --git a/IPA.Injector/GameVersionEarly.cs b/IPA.Injector/GameVersionEarly.cs
index e6e0280c..5ed1ace7 100644
--- a/IPA.Injector/GameVersionEarly.cs
+++ b/IPA.Injector/GameVersionEarly.cs
@@ -1,4 +1,5 @@
-using IPA.Utilities;
+#nullable enable
+using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
@@ -48,7 +49,7 @@ namespace IPA.Injector
}
var rewind = -sizeof(int) - sizeof(byte);
- stream.Seek(rewind, SeekOrigin.Current); // rewind to the string length
+ _ = stream.Seek(rewind, SeekOrigin.Current); // rewind to the string length
var strlen = reader.ReadInt32();
var strbytes = reader.ReadBytes(strlen);
@@ -57,7 +58,7 @@ namespace IPA.Injector
}
}
- internal static AlmostVersion SafeParseVersion() => new AlmostVersion(GetGameVersion());
+ internal static AlmostVersion SafeParseVersion() => new(GetGameVersion());
private static void _Load()
{
diff --git a/IPA.Injector/IPA.Injector.csproj b/IPA.Injector/IPA.Injector.csproj
index a100bf44..1fbe99af 100644
--- a/IPA.Injector/IPA.Injector.csproj
+++ b/IPA.Injector/IPA.Injector.csproj
@@ -1,18 +1,18 @@
-
-
+
+
- net461;net35
+ net472
IPA.Injector
-
+
true
false
false
- true
+ true
-
+
$(DefineConstants);NET4
@@ -21,14 +21,14 @@
$(DefineConstants);BeatSaber
-
+
-
+
-
+
..\Refs\UnityEngine.CoreModule.Net4.dll
False
@@ -37,8 +37,8 @@
False
-
-
+
+
Libraries\Mono\I18N.dll
Always
@@ -55,7 +55,19 @@
Libraries\Mono\System.Runtime.Serialization.dll
Always
+
+ Libraries\Mono\netstandard.dll
+ Always
+
+
+
+
+ Libraries\Thirdparty\%(Filename)%(Extension)
+ Always
+
+
+
Libraries\Mono\I18N.dll
@@ -70,13 +82,22 @@
Always
-
+
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
@@ -91,7 +112,7 @@
-
+
@@ -102,6 +123,6 @@
-
+
diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs
index 1ea7e058..7d338af7 100644
--- a/IPA.Injector/Injector.cs
+++ b/IPA.Injector/Injector.cs
@@ -1,4 +1,6 @@
-using IPA.Config;
+#nullable enable
+using IPA.AntiMalware;
+using IPA.Config;
using IPA.Injector.Backups;
using IPA.Loader;
using IPA.Logging;
@@ -29,8 +31,8 @@ namespace IPA.Injector
// ReSharper disable once UnusedMember.Global
internal static class Injector
{
- private static Task pluginAsyncLoadTask;
- private static Task permissionFixTask;
+ private static Task? pluginAsyncLoadTask;
+ private static Task? permissionFixTask;
//private static string otherNewtonsoftJson = null;
// ReSharper disable once UnusedParameter.Global
@@ -40,7 +42,7 @@ namespace IPA.Injector
// and since this class doesn't have any static fields that
// aren't defined in mscorlib, we can control exactly what
// gets loaded.
-
+ _ = args;
try
{
if (Environment.GetCommandLineArgs().Contains("--verbose"))
@@ -48,16 +50,6 @@ namespace IPA.Injector
SetupLibraryLoading();
- /*var otherNewtonsoft = Path.Combine(
- Directory.EnumerateDirectories(Environment.CurrentDirectory, "*_Data").First(),
- "Managed",
- "Newtonsoft.Json.dll");
- if (File.Exists(otherNewtonsoft))
- { // this game ships its own Newtonsoft; force load ours and flag loading theirs
- LibLoader.LoadLibrary(new AssemblyName("Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"));
- otherNewtonsoftJson = otherNewtonsoft;
- }*/
-
EnsureDirectories();
// this is weird, but it prevents Mono from having issues loading the type.
@@ -71,7 +63,7 @@ namespace IPA.Injector
*/
#endregion
- log.Debug("Initializing logger");
+ Default.Debug("Initializing logger");
SelfConfig.ReadCommandLine(Environment.GetCommandLineArgs());
SelfConfig.Load();
@@ -79,23 +71,27 @@ namespace IPA.Injector
if (AntiPiracy.IsInvalid(Environment.CurrentDirectory))
{
- log.Error("Invalid installation; please buy the game to run BSIPA.");
+ Default.Error("Invalid installation; please buy the game to run BSIPA.");
return;
}
CriticalSection.Configure();
- injector.Debug("Prepping bootstrapper");
+ Logging.Logger.Injector.Debug("Prepping bootstrapper");
+
+ // make sure to load the game version and check boundaries before installing the bootstrap, because that uses the game assemblies property
+ GameVersionEarly.Load();
+ SelfConfig.Instance.CheckVersionBoundary();
// updates backup
InstallBootstrapPatch();
- GameVersionEarly.Load();
+ AntiMalwareEngine.Initialize();
Updates.InstallPendingUpdates();
- LibLoader.SetupAssemblyFilenames(true);
+ Loader.LibLoader.SetupAssemblyFilenames(true);
pluginAsyncLoadTask = PluginLoader.LoadTask();
permissionFixTask = PermissionFix.FixPermissions(new DirectoryInfo(Environment.CurrentDirectory));
@@ -110,16 +106,16 @@ namespace IPA.Injector
{
string path;
if (!Directory.Exists(path = Path.Combine(Environment.CurrentDirectory, "UserData")))
- Directory.CreateDirectory(path);
+ _ = Directory.CreateDirectory(path);
if (!Directory.Exists(path = Path.Combine(Environment.CurrentDirectory, "Plugins")))
- Directory.CreateDirectory(path);
+ _ = Directory.CreateDirectory(path);
}
private static void SetupLibraryLoading()
{
if (loadingDone) return;
loadingDone = true;
- LibLoader.Configure();
+ Loader.LibLoader.Configure();
}
private static void InstallHarmonyProtections()
@@ -137,13 +133,13 @@ namespace IPA.Injector
var dataDir = new DirectoryInfo(managedPath).Parent.Name;
var gameName = dataDir.Substring(0, dataDir.Length - 5);
- injector.Debug("Finding backup");
+ Logging.Logger.Injector.Debug("Finding backup");
var backupPath = Path.Combine(Environment.CurrentDirectory, "IPA", "Backups", gameName);
var bkp = BackupManager.FindLatestBackup(backupPath);
if (bkp == null)
- injector.Warn("No backup found! Was BSIPA installed using the installer?");
+ Logging.Logger.Injector.Warn("No backup found! Was BSIPA installed using the installer?");
- injector.Debug("Ensuring patch on UnityEngine.CoreModule exists");
+ Logging.Logger.Injector.Debug("Ensuring patch on UnityEngine.CoreModule exists");
#region Insert patch into UnityEngine.CoreModule.dll
@@ -179,12 +175,12 @@ namespace IPA.Injector
if (application == null)
{
- injector.Critical("UnityEngine.CoreModule doesn't have a definition for UnityEngine.Camera!"
+ Logging.Logger.Injector.Critical("UnityEngine.CoreModule doesn't have a definition for UnityEngine.Camera!"
+ "Nothing to patch to get ourselves into the Unity run cycle!");
goto endPatchCoreModule;
}
- MethodDefinition cctor = null;
+ MethodDefinition? cctor = null;
foreach (var m in application.Methods)
if (m.IsRuntimeSpecialName && m.Name == ".cctor")
cctor = m;
@@ -244,7 +240,7 @@ namespace IPA.Injector
endPatchCoreModule:
#endregion Insert patch into UnityEngine.CoreModule.dll
- injector.Debug("Ensuring game assemblies are virtualized");
+ Logging.Logger.Injector.Debug("Ensuring game assemblies are virtualized");
#region Virtualize game assemblies
bool isFirst = true;
@@ -256,15 +252,15 @@ namespace IPA.Injector
try
{
- injector.Debug($"Virtualizing {name}");
+ Logging.Logger.Injector.Debug($"Virtualizing {name}");
using var ascModule = VirtualizedModule.Load(ascPath);
ascModule.Virtualize(cAsmName, () => bkp?.Add(ascPath));
}
catch (Exception e)
{
- injector.Error($"Could not virtualize {ascPath}");
+ Logging.Logger.Injector.Error($"Could not virtualize {ascPath}");
if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
- injector.Error(e);
+ Logging.Logger.Injector.Error(e);
}
#if BeatSaber
@@ -272,7 +268,7 @@ namespace IPA.Injector
{
try
{
- injector.Debug("Applying anti-yeet patch");
+ Logging.Logger.Injector.Debug("Applying anti-yeet patch");
using var ascAsmDef = AssemblyDefinition.ReadAssembly(ascPath, new ReaderParameters
{
@@ -291,9 +287,9 @@ namespace IPA.Injector
}
catch (Exception e)
{
- injector.Warn($"Could not apply anti-yeet patch to {ascPath}");
+ Logging.Logger.Injector.Warn($"Could not apply anti-yeet patch to {ascPath}");
if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
- injector.Warn(e);
+ Logging.Logger.Injector.Warn(e);
}
}
#endif
@@ -301,7 +297,7 @@ namespace IPA.Injector
#endregion
sw.Stop();
- injector.Info($"Installing bootstrapper took {sw.Elapsed}");
+ Logging.Logger.Injector.Info($"Installing bootstrapper took {sw.Elapsed}");
}
private static bool bootstrapped;
@@ -311,10 +307,6 @@ namespace IPA.Injector
if (bootstrapped) return;
bootstrapped = true;
- /*if (otherNewtonsoftJson != null)
- Assembly.LoadFrom(otherNewtonsoftJson);*/
-
-
Application.logMessageReceived += delegate (string condition, string stackTrace, LogType type)
{
var level = UnityLogRedirector.LogTypeToLevel(type);
@@ -322,6 +314,8 @@ namespace IPA.Injector
UnityLogProvider.UnityLogger.Log(level, $"{stackTrace}");
};
+ StdoutInterceptor.EnsureHarmonyLogging();
+
// need to reinit streams singe Unity seems to redirect stdout
StdoutInterceptor.RedirectConsole();
@@ -336,12 +330,12 @@ namespace IPA.Injector
private static void Bootstrapper_Destroyed()
{
// wait for plugins to finish loading
- pluginAsyncLoadTask.Wait();
- permissionFixTask.Wait();
+ pluginAsyncLoadTask?.Wait();
+ permissionFixTask?.Wait();
- log.Debug("Plugins loaded");
- log.Debug(string.Join(", ", PluginLoader.PluginsMetadata.StrJP()));
- PluginComponent.Create();
+ Default.Debug("Plugins loaded");
+ Default.Debug(string.Join(", ", PluginLoader.PluginsMetadata.StrJP()));
+ _ = PluginComponent.Create();
}
}
}
\ No newline at end of file
diff --git a/IPA.Injector/PermissionFix.cs b/IPA.Injector/PermissionFix.cs
index 7b531e52..152c6435 100644
--- a/IPA.Injector/PermissionFix.cs
+++ b/IPA.Injector/PermissionFix.cs
@@ -1,73 +1,73 @@
using IPA.Logging;
-using System;
+using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Security.AccessControl;
-using System.Security.Principal;
-using System.Threading.Tasks;
-#if NET3
-using Net3_Proxy;
-#endif
-
-namespace IPA.Injector
-{
- internal static class PermissionFix
+using System.IO;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using System.Threading.Tasks;
+#if NET3
+using Net3_Proxy;
+#endif
+
+namespace IPA.Injector
+{
+ internal static class PermissionFix
{
[SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler",
Justification = "I very explicitly want the default scheduler")]
- public static Task FixPermissions(DirectoryInfo root)
- {
- if (!root.Exists) return new Task(() => { });
-
- return Task.Factory.StartNew(() =>
- {
- var sw = Stopwatch.StartNew();
- try
- {
- var acl = root.GetAccessControl();
-
- var rules = acl.GetAccessRules(true, true, typeof(SecurityIdentifier));
-
- var requestedRights = FileSystemRights.Modify;
- var requestedInheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
- var requestedPropagation = PropagationFlags.InheritOnly;
-
- bool hasRule = false;
- for (var i = 0; i < rules.Count; i++)
- {
- var rule = rules[i];
-
- if (rule is FileSystemAccessRule fsrule
- && fsrule.AccessControlType == AccessControlType.Allow
- && fsrule.InheritanceFlags.HasFlag(requestedInheritance)
- && fsrule.PropagationFlags == requestedPropagation
- && fsrule.FileSystemRights.HasFlag(requestedRights))
- { hasRule = true; break; }
- }
-
- if (!hasRule)
- { // this is *sooo* fucking slow on first run
- acl.AddAccessRule(
- new FileSystemAccessRule(
- new SecurityIdentifier(WellKnownSidType.WorldSid, null),
- requestedRights,
- requestedInheritance,
- requestedPropagation,
- AccessControlType.Allow
- )
- );
- root.SetAccessControl(acl);
- }
- }
- catch (Exception e)
- {
- Logger.log.Warn("Error configuring permissions in the game install dir");
- Logger.log.Warn(e);
- }
- sw.Stop();
- Logger.log.Info($"Configuring permissions took {sw.Elapsed}");
- });
- }
- }
-}
+ public static Task FixPermissions(DirectoryInfo root)
+ {
+ if (!root.Exists) return new Task(() => { });
+
+ return Task.Factory.StartNew(() =>
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ var acl = root.GetAccessControl();
+
+ var rules = acl.GetAccessRules(true, true, typeof(SecurityIdentifier));
+
+ var requestedRights = FileSystemRights.Modify;
+ var requestedInheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
+ var requestedPropagation = PropagationFlags.InheritOnly;
+
+ bool hasRule = false;
+ for (var i = 0; i < rules.Count; i++)
+ {
+ var rule = rules[i];
+
+ if (rule is FileSystemAccessRule fsrule
+ && fsrule.AccessControlType == AccessControlType.Allow
+ && fsrule.InheritanceFlags.HasFlag(requestedInheritance)
+ && fsrule.PropagationFlags == requestedPropagation
+ && fsrule.FileSystemRights.HasFlag(requestedRights))
+ { hasRule = true; break; }
+ }
+
+ if (!hasRule)
+ { // this is *sooo* fucking slow on first run
+ acl.AddAccessRule(
+ new FileSystemAccessRule(
+ new SecurityIdentifier(WellKnownSidType.WorldSid, null),
+ requestedRights,
+ requestedInheritance,
+ requestedPropagation,
+ AccessControlType.Allow
+ )
+ );
+ root.SetAccessControl(acl);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Default.Warn("Error configuring permissions in the game install dir");
+ Logger.Default.Warn(e);
+ }
+ sw.Stop();
+ Logger.Default.Info($"Configuring permissions took {sw.Elapsed}");
+ });
+ }
+ }
+}
diff --git a/IPA.Injector/Properties/AssemblyInfo.cs b/IPA.Injector/Properties/AssemblyInfo.cs
index 5f18531f..bdafdad9 100644
--- a/IPA.Injector/Properties/AssemblyInfo.cs
+++ b/IPA.Injector/Properties/AssemblyInfo.cs
@@ -1,37 +1,39 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("IPA.Injector")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("IPA.Injector")]
-[assembly: AssemblyCopyright("Copyright © 2018")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("2a1af16b-27f1-46e0-9a95-181516bc1cb7")]
-[assembly: InternalsVisibleTo("IPA.Loader")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion(IPA.Config.SelfConfig.IPAVersion)]
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("IPA.Injector")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("IPA.Injector")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+[assembly: CLSCompliant(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2a1af16b-27f1-46e0-9a95-181516bc1cb7")]
+[assembly: InternalsVisibleTo("IPA.Loader")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion(IPA.Config.SelfConfig.IPAVersion)]
[assembly: AssemblyFileVersion(IPA.Config.SelfConfig.IPAVersion)]
\ No newline at end of file
diff --git a/IPA.Injector/Updates.cs b/IPA.Injector/Updates.cs
index 1862f889..7822e209 100644
--- a/IPA.Injector/Updates.cs
+++ b/IPA.Injector/Updates.cs
@@ -1,4 +1,7 @@
-using IPA.Utilities;
+#nullable enable
+using IPA.AntiMalware;
+using IPA.Config;
+using IPA.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -36,14 +39,27 @@ namespace IPA.Injector
if (ipaVersion > selfVersion)
{
+ var scanResult = AntiMalwareEngine.Engine.ScanFile(new FileInfo(path));
+ if (scanResult == ScanResult.Detected)
+ {
+ Updater.Error("Scan of BSIPA installer found malware; not updating");
+ return;
+ }
+ if (!SelfConfig.AntiMalware_.RunPartialThreatCode_ && scanResult is not ScanResult.KnownSafe and not ScanResult.NotDetected)
+ {
+ Updater.Error("Scan of BSIPA installer returned partial threat; not updating. To allow this, enable AntiMalware.RunPartialThreatCode in the config.");
+ return;
+ }
+
_ = Process.Start(new ProcessStartInfo
{
FileName = path,
- Arguments = $"\"-nw={Process.GetCurrentProcess().Id},s={string.Join(" ", Environment.GetCommandLineArgs().Skip(1).StrJP()).Replace("\\", "\\\\").Replace(",", "\\,")}\"",
+ Arguments = $"\"-nw={Process.GetCurrentProcess().Id}," +
+ $"s={string.Join(" ", Environment.GetCommandLineArgs().Skip(1).StrJP()).Replace("\\", "\\\\").Replace(",", "\\,")}\"",
UseShellExecute = false
});
- updater.Info("Updating BSIPA...");
+ Updater.Info("Updating BSIPA...");
Environment.Exit(0);
}
}
@@ -54,7 +70,7 @@ namespace IPA.Injector
if (!Directory.Exists(pendingDir)) return;
// there are pending updates, install
- updater.Info("Installing pending updates");
+ Updater.Info("Installing pending updates");
var toDelete = Array.Empty();
var delFn = Path.Combine(pendingDir, DeleteFileName);
@@ -72,8 +88,8 @@ namespace IPA.Injector
}
catch (Exception e)
{
- updater.Error("While trying to install pending updates: Error deleting file marked for deletion");
- updater.Error(e);
+ Updater.Error("While trying to install pending updates: Error deleting file marked for deletion");
+ Updater.Error(e);
}
}
@@ -98,12 +114,12 @@ namespace IPA.Injector
}
catch (UnauthorizedAccessException e)
{
- updater.Error(e);
+ Updater.Error(e);
continue;
}
catch (DirectoryNotFoundException e)
{
- updater.Error(e);
+ Updater.Error(e);
continue;
}
@@ -116,7 +132,7 @@ namespace IPA.Injector
}
catch (FileNotFoundException e)
{
- updater.Error(e);
+ Updater.Error(e);
}
}
@@ -137,15 +153,15 @@ namespace IPA.Injector
{
Utils.CopyAll(new DirectoryInfo(pendingDir), new DirectoryInfo(UnityGame.InstallPath), onCopyException: (e, f) =>
{
- updater.Error($"Error copying file {Utils.GetRelativePath(f.FullName, pendingDir)} from Pending:");
- updater.Error(e);
+ Updater.Error($"Error copying file {Utils.GetRelativePath(f.FullName, pendingDir)} from Pending:");
+ Updater.Error(e);
return true;
});
}
catch (Exception e)
{
- updater.Error("While trying to install pending updates: Error copying files in");
- updater.Error(e);
+ Updater.Error("While trying to install pending updates: Error copying files in");
+ Updater.Error(e);
}
try
@@ -154,8 +170,8 @@ namespace IPA.Injector
}
catch (Exception e)
{
- updater.Error("Something went wrong performing an operation that should never fail!");
- updater.Error(e);
+ Updater.Error("Something went wrong performing an operation that should never fail!");
+ Updater.Error(e);
}
}
}
diff --git a/IPA.Loader/AntiMalware/AmsiConstants.cs b/IPA.Loader/AntiMalware/AmsiConstants.cs
new file mode 100644
index 00000000..d77aa55c
--- /dev/null
+++ b/IPA.Loader/AntiMalware/AmsiConstants.cs
@@ -0,0 +1,14 @@
+#nullable enable
+using System;
+
+namespace IPA.AntiMalware
+{
+ internal static class AmsiConstants
+ {
+ public const string AppName = "BSIPA/" + Config.SelfConfig.IPAVersion;
+ public const string IAntimalwareGuidStr = "82d29c2e-f062-44e6-b5c9-3d9a2f24a2df";
+ public static readonly Guid IAntimalwareGuid = new(IAntimalwareGuidStr);
+ public static readonly Guid CAntimalwareGuid = new("fdb00e52-a214-4aa1-8fba-4357bb0072ec");
+ public static readonly Guid IUnknownGuid = new("00000000-0000-0000-C000-000000000046");
+ }
+}
diff --git a/IPA.Loader/AntiMalware/AmsiResult.cs b/IPA.Loader/AntiMalware/AmsiResult.cs
new file mode 100644
index 00000000..ff755180
--- /dev/null
+++ b/IPA.Loader/AntiMalware/AmsiResult.cs
@@ -0,0 +1,13 @@
+#nullable enable
+
+namespace IPA.AntiMalware
+{
+ internal enum AmsiResult
+ {
+ Clean = 0,
+ NotDetected = 1,
+ BlockedByAdminStart = 0x4000,
+ BlockedByAdminEnd = 0x4fff,
+ Detected = 32768
+ }
+}
diff --git a/IPA.Loader/AntiMalware/AntiMalwareEngine.cs b/IPA.Loader/AntiMalware/AntiMalwareEngine.cs
new file mode 100644
index 00000000..dc9aef79
--- /dev/null
+++ b/IPA.Loader/AntiMalware/AntiMalwareEngine.cs
@@ -0,0 +1,46 @@
+#nullable enable
+using IPA.Config;
+using IPA.Logging;
+using System;
+
+namespace IPA.AntiMalware
+{
+ ///
+ /// Provides a way to access BSIPA's Anti-Malware engine.
+ ///
+ ///
+ ///
+ public static class AntiMalwareEngine
+ {
+ private static IAntiMalware? engine;
+
+ ///
+ /// Gets the current Anti-Malware engine.
+ ///
+ public static IAntiMalware Engine => engine ?? throw new InvalidOperationException();
+
+ internal static bool IsInitialized => engine != null;
+
+ internal static void Initialize()
+ {
+ engine = CreateEngine();
+ }
+
+ private static IAntiMalware CreateEngine()
+ {
+ IAntiMalware? engine = null;
+ if (SelfConfig.AntiMalware_.UseIfAvailable_)
+ {
+#if !NET35
+ engine = WindowsCOMAntiMalware.TryInitialize();
+#endif
+ engine ??= WindowsWin32AntiMalware.TryInitialize();
+ }
+ engine ??= new NoopAntiMalware();
+
+ Logger.AntiMalware.Debug($"Antimalware engine initialized with {engine.GetType()}");
+
+ return engine;
+ }
+ }
+}
diff --git a/IPA.Loader/AntiMalware/IAntiMalware.cs b/IPA.Loader/AntiMalware/IAntiMalware.cs
new file mode 100644
index 00000000..5c8d37a6
--- /dev/null
+++ b/IPA.Loader/AntiMalware/IAntiMalware.cs
@@ -0,0 +1,25 @@
+#nullable enable
+using System.IO;
+
+namespace IPA.AntiMalware
+{
+ ///
+ /// An Anti-Malware engine that can be used to scan and detect potentially harmful files.
+ ///
+ public interface IAntiMalware
+ {
+ ///
+ /// Scans a particular file for malware.
+ ///
+ /// The file to scan.
+ /// A indicating whether the file is safe or not.
+ ScanResult ScanFile(FileInfo file);
+ ///
+ /// Scans a particular in-memory blob for malware.
+ ///
+ /// The binary blob to scan.
+ /// The name of the content. If this is left , one will be automatically generated.
+ /// A indicating whether the file is safe or not.
+ ScanResult ScanData(byte[] data, string? contentName = null);
+ }
+}
diff --git a/IPA.Loader/AntiMalware/NoopAntiMalware.cs b/IPA.Loader/AntiMalware/NoopAntiMalware.cs
new file mode 100644
index 00000000..60609898
--- /dev/null
+++ b/IPA.Loader/AntiMalware/NoopAntiMalware.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware
+{
+ internal class NoopAntiMalware : IAntiMalware
+ {
+ public ScanResult ScanData(byte[] data, string contentName = null) => ScanResult.NotDetected;
+ public ScanResult ScanFile(FileInfo file) => ScanResult.NotDetected;
+ }
+}
diff --git a/IPA.Loader/AntiMalware/ScanResult.cs b/IPA.Loader/AntiMalware/ScanResult.cs
new file mode 100644
index 00000000..43d501eb
--- /dev/null
+++ b/IPA.Loader/AntiMalware/ScanResult.cs
@@ -0,0 +1,27 @@
+
+namespace IPA.AntiMalware
+{
+ ///
+ /// The result of an Anti-Malware scan.
+ ///
+ public enum ScanResult
+ {
+ ///
+ /// The object is known to be safe.
+ ///
+ KnownSafe,
+ ///
+ /// No malware was detected, but it is not known to be safe.
+ ///
+ NotDetected,
+ ///
+ /// Malware was detected, and the content should not be executed.
+ ///
+ Detected,
+ ///
+ /// The malware engine returned a threat level less than the max, so this object may be dangerous.
+ /// Proceed with caution.
+ ///
+ MaybeMalware
+ }
+}
diff --git a/IPA.Loader/AntiMalware/WindowsWin32AntiMalware.cs b/IPA.Loader/AntiMalware/WindowsWin32AntiMalware.cs
new file mode 100644
index 00000000..43bfba69
--- /dev/null
+++ b/IPA.Loader/AntiMalware/WindowsWin32AntiMalware.cs
@@ -0,0 +1,111 @@
+#nullable enable
+using IPA.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware
+{
+ internal class WindowsWin32AntiMalware : IAntiMalware, IDisposable
+ {
+ internal static WindowsWin32AntiMalware? TryInitialize()
+ {
+ try
+ {
+ return new();
+ }
+ catch (Exception e)
+ {
+ Logger.AntiMalware.Warn("Could not initialize Win32-based antimalware engine:");
+ Logger.AntiMalware.Warn(e);
+ return null;
+ }
+ }
+
+ private readonly IntPtr handle;
+ private bool disposedValue;
+
+ private WindowsWin32AntiMalware()
+ {
+ AmsiInitialize(AmsiConstants.AppName, out handle);
+ }
+
+ private static ScanResult ScanResultFromAmsiResult(AmsiResult result)
+ => result switch
+ {
+ AmsiResult.Clean => ScanResult.KnownSafe,
+ AmsiResult.NotDetected => ScanResult.NotDetected,
+ AmsiResult.Detected => ScanResult.Detected,
+ _ => ScanResult.MaybeMalware
+ };
+
+ public ScanResult ScanFile(FileInfo file)
+ {
+ var data = File.ReadAllBytes(file.FullName);
+ return ScanData(data, file.FullName);
+ }
+
+ public ScanResult ScanData(byte[] data, string? contentName = null)
+ {
+ contentName ??= $"unknown_data_{Guid.NewGuid()}";
+
+ AmsiScanBuffer(handle, data, (uint)data.Length, contentName, IntPtr.Zero, out var result);
+
+ Logger.AntiMalware.Trace($"Scanned data named '{contentName}' and got '{result}'");
+ return ScanResultFromAmsiResult(result);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // we have no disposable managed state
+ }
+
+ AmsiUninitialize(handle);
+ disposedValue = true;
+ }
+ }
+
+ ~WindowsWin32AntiMalware()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
+#if !NET35
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+#endif
+ private static extern void AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)] string appName, [Out] out IntPtr handle);
+
+ [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
+#if !NET35
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+#endif
+ private static extern void AmsiUninitialize(IntPtr handle);
+
+ [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
+#if !NET35
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+#endif
+ private static extern void AmsiScanBuffer(IntPtr context,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buffer, uint length,
+ [MarshalAs(UnmanagedType.LPWStr)] string contentName,
+ IntPtr session,
+ [Out] out AmsiResult result);
+ }
+}
diff --git a/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiFileStream.cs b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiFileStream.cs
new file mode 100644
index 00000000..be0d383f
--- /dev/null
+++ b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiFileStream.cs
@@ -0,0 +1,113 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware.ComAPI
+{
+ internal class AmsiFileStream : IAmsiStream, IDisposable
+ {
+ private readonly FileInfo file;
+ private readonly IntPtr session;
+
+ public AmsiFileStream(FileInfo file, IntPtr session)
+ {
+ this.file = file;
+ this.session = session;
+ }
+
+ public unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, out uint writtenData)
+ {
+ switch (attribute)
+ {
+ case AmsiAttribute.AppName:
+ writtenData = WriteWString(AmsiConstants.AppName, dataSize, buffer);
+ return;
+ case AmsiAttribute.Session:
+ *(IntPtr*)buffer = session;
+ writtenData = (uint)sizeof(IntPtr);
+ return;
+
+ case AmsiAttribute.ContentName:
+ writtenData = WriteWString(file.FullName, dataSize, buffer);
+ return;
+
+ case AmsiAttribute.ContentSize:
+ *(ulong*)buffer = (ulong)file.Length;
+ writtenData = sizeof(ulong);
+ return;
+
+ default:
+ throw new NotImplementedException(); // return e_notimpl
+ }
+
+ static unsafe uint WriteWString(string str, uint dataSize, byte* buffer)
+ {
+ fixed (char* name = str)
+ {
+ return (uint)Encoding.Unicode.GetBytes(name, str.Length, buffer, (int)dataSize);
+ }
+ }
+ }
+
+ private FileStream? stream;
+ private bool disposedValue;
+ private readonly byte[] readBuffer = new byte[1024];
+
+ public unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, out uint readSize)
+ {
+ stream ??= file.OpenRead();
+
+ stream.Position = (long)position;
+
+ var bytesToRead = dataSize;
+ readSize = 0;
+
+ while (bytesToRead > 0)
+ {
+ var bytesRead = stream.Read(readBuffer, 0, (int)Math.Min(readBuffer.Length, bytesToRead));
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ fixed (byte* readBufferPtr = readBuffer)
+ {
+ Buffer.MemoryCopy(readBufferPtr, buffer + readSize, dataSize - readSize, bytesRead);
+ }
+ bytesToRead -= (uint)bytesRead;
+ readSize += (uint)bytesRead;
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ stream?.Dispose();
+ }
+
+ disposedValue = true;
+ }
+ }
+
+ // This does not have unmanagd resources, so it doesn't need to exist
+ // ~AmsiFileStream()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiMemoryStream.cs b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiMemoryStream.cs
new file mode 100644
index 00000000..a611fa7a
--- /dev/null
+++ b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiMemoryStream.cs
@@ -0,0 +1,114 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware.ComAPI
+{
+ internal class AmsiMemoryStream : IAmsiStream, IDisposable
+ {
+ private readonly string contentName;
+ private readonly byte[] data;
+ private readonly GCHandle dataHandle;
+
+ private readonly IntPtr session;
+ private bool disposedValue;
+
+ public AmsiMemoryStream(string contentName, byte[] data, IntPtr session)
+ {
+ this.data = data;
+ dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
+ this.session = session;
+ this.contentName = contentName;
+ }
+
+ public unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, out uint writtenData)
+ {
+ switch (attribute)
+ {
+ case AmsiAttribute.AppName:
+ writtenData = WriteWString(AmsiConstants.AppName, dataSize, buffer);
+ return;
+ case AmsiAttribute.Session:
+ *(IntPtr*)buffer = session;
+ writtenData = (uint)sizeof(IntPtr);
+ return;
+
+ case AmsiAttribute.ContentName:
+ writtenData = WriteWString(contentName, dataSize, buffer);
+ return;
+
+ case AmsiAttribute.ContentSize:
+ *(ulong*)buffer = (ulong)data.Length;
+ writtenData = sizeof(ulong);
+ return;
+
+ case AmsiAttribute.ContentAddress:
+ // because our data is pinned, it can't move while this object exists so we can pass out the fixed address
+ fixed (byte* dataAddr = data)
+ {
+ *(byte**)buffer = dataAddr;
+ }
+ writtenData = (uint)sizeof(IntPtr);
+ return;
+
+ default:
+ throw new NotImplementedException(); // return e_notimpl
+ }
+
+ static unsafe uint WriteWString(string str, uint dataSize, byte* buffer)
+ {
+ fixed (char* name = str)
+ {
+ return (uint)Encoding.Unicode.GetBytes(name, str.Length, buffer, (int)dataSize);
+ }
+ }
+ }
+
+ public unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, out uint readSize)
+ {
+ if (position >= (ulong)data.Length)
+ {
+ throw new EndOfStreamException();
+ }
+
+ fixed (byte* dataPtr = data)
+ {
+ var toRead = Math.Min((ulong)data.Length - position, dataSize);
+ Buffer.MemoryCopy(dataPtr + position, buffer, dataSize, toRead);
+ readSize = (uint)toRead;
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // no managed stae to dispose
+ }
+
+ dataHandle.Free();
+ disposedValue = true;
+ }
+ }
+
+ ~AmsiMemoryStream()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/IAntimalware.cs b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/IAntimalware.cs
new file mode 100644
index 00000000..109fce79
--- /dev/null
+++ b/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/IAntimalware.cs
@@ -0,0 +1,52 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware.ComAPI
+{
+ [ComImport]
+ [Guid(AmsiConstants.IAntimalwareGuidStr)]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAntimalware
+ {
+ void Scan([In] IAmsiStream stream, [Out] out AmsiResult result, [Out] out IAntimalwareProvider provider);
+ void CloseSession([In] ulong session);
+ }
+
+ [ComImport]
+ [Guid("3e47f2e5-81d4-4d3b-897f-545096770373")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAmsiStream
+ {
+ unsafe void GetAttribute([In] AmsiAttribute attribute, [In] uint dataSize, [Out] byte* buffer, [Out] out uint writtenData);
+ unsafe void Read([In] ulong position, [In] uint dataSize, [Out] byte* buffer, [Out] out uint readSize);
+ }
+
+ [ComImport]
+ [Guid("b2cabfe3-fe04-42b1-a5df-08d483d4d125")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAntimalwareProvider
+ {
+ [return: MarshalAs(UnmanagedType.LPWStr)] string DisplayName();
+
+ AmsiResult Scan([In] IAmsiStream stream);
+ void CloseSession([In] ulong session);
+ }
+
+ internal enum AmsiAttribute
+ {
+ AppName = 0,
+ ContentName = 1,
+ ContentSize = 2,
+ ContentAddress = 3,
+ Session = 4,
+ RedirectChainSize = 5,
+ RedirectChainAddress = 6,
+ AllSize = 7,
+ AllAddress = 8,
+ }
+}
diff --git a/IPA.Loader/AntiMalware/_HideInNet3/WindowsCOMAntiMalware.cs b/IPA.Loader/AntiMalware/_HideInNet3/WindowsCOMAntiMalware.cs
new file mode 100644
index 00000000..70868bd2
--- /dev/null
+++ b/IPA.Loader/AntiMalware/_HideInNet3/WindowsCOMAntiMalware.cs
@@ -0,0 +1,88 @@
+#nullable enable
+using IPA.AntiMalware.ComAPI;
+using IPA.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.AntiMalware
+{
+ internal class WindowsCOMAntiMalware : IAntiMalware
+ {
+ internal static WindowsCOMAntiMalware? TryInitialize()
+ {
+ // Mono's COM interop *fundamentally doesn't work.*
+ // End of story.
+#if false
+ try
+ {
+ return new();
+ }
+ catch (Exception e)
+ {
+ Logger.AntiMalware.Warn("Could not initialize COM-based antimalware engine:");
+ Logger.AntiMalware.Warn(e);
+ }
+#endif
+ return null;
+ }
+
+ private readonly IAntimalware amInterface;
+
+ private WindowsCOMAntiMalware()
+ {
+ var hr = CoCreateInstanceAM(AmsiConstants.CAntimalwareGuid,
+ null,
+ 0x1 | 0x4 /* inproc server, local server */,
+ AmsiConstants.IAntimalwareGuid,
+ out var antimalware);
+ Marshal.ThrowExceptionForHR(hr);
+
+ amInterface = antimalware;
+ }
+
+ [DllImport("ole32",
+ CallingConvention = CallingConvention.Winapi,
+ ExactSpelling = true,
+ PreserveSig = false,
+ EntryPoint = "CoCreateInstance")]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ private static extern int CoCreateInstanceAM(
+ [In] in Guid clsid,
+ [In, MarshalAs(UnmanagedType.Interface)] object? unkOuter,
+ [In] int dwClsContext,
+ [In] in Guid iid,
+ [Out, MarshalAs(UnmanagedType.Interface)] out IAntimalware @interface);
+
+
+ private static ScanResult ScanResultFromAmsiResult(AmsiResult result)
+ => result switch
+ {
+ AmsiResult.Clean => ScanResult.KnownSafe,
+ AmsiResult.NotDetected => ScanResult.NotDetected,
+ AmsiResult.Detected => ScanResult.Detected,
+ _ => ScanResult.MaybeMalware
+ };
+
+ public ScanResult ScanFile(FileInfo file)
+ {
+ using var stream = new AmsiFileStream(file, IntPtr.Zero);
+ amInterface.Scan(stream, out var result, out var provider);
+ Logger.AntiMalware.Trace($"Scanned file '{file}' with {provider.DisplayName()}, and got '{result}'");
+ return ScanResultFromAmsiResult(result);
+ }
+
+ public ScanResult ScanData(byte[] data, string? contentName = null)
+ {
+ contentName ??= $"unknown_data_{Guid.NewGuid()}";
+ using var stream = new AmsiMemoryStream(contentName, data, IntPtr.Zero);
+ amInterface.Scan(stream, out var result, out var provider);
+ Logger.AntiMalware.Trace($"Scanned data named '{contentName}' with {provider.DisplayName()}, and got '{result}'");
+ return ScanResultFromAmsiResult(result);
+ }
+ }
+}
diff --git a/IPA.Loader/Config/Config.cs b/IPA.Loader/Config/Config.cs
index 0e4c19f1..d818e5b8 100644
--- a/IPA.Loader/Config/Config.cs
+++ b/IPA.Loader/Config/Config.cs
@@ -81,7 +81,7 @@ namespace IPA.Config
/// Registers a to use for configs.
///
/// the type to register
- public static void Register() where T : IConfigProvider => Register(typeof(T));
+ public static void Register() where T : IConfigProvider, new() => Register(typeof(T));
///
/// Registers a to use for configs.
diff --git a/IPA.Loader/Config/ConfigRuntime.cs b/IPA.Loader/Config/ConfigRuntime.cs
index d9c4a203..baf5138f 100644
--- a/IPA.Loader/Config/ConfigRuntime.cs
+++ b/IPA.Loader/Config/ConfigRuntime.cs
@@ -185,8 +185,8 @@ namespace IPA.Config
}
catch (Exception e)
{
- Logger.config.Error($"{nameof(IConfigStore)} for {config.File} errored while writing to disk");
- Logger.config.Error(e);
+ Logger.Config.Error($"{nameof(IConfigStore)} for {config.File} errored while writing to disk");
+ Logger.Config.Error(e);
}
}
@@ -209,8 +209,8 @@ namespace IPA.Config
}
catch (Exception e)
{
- Logger.config.Error($"{nameof(IConfigStore)} for {config.File} errored while reading from the {nameof(IConfigProvider)}");
- Logger.config.Error(e);
+ Logger.Config.Error($"{nameof(IConfigStore)} for {config.File} errored while reading from the {nameof(IConfigProvider)}");
+ Logger.Config.Error(e);
}
}
@@ -235,8 +235,8 @@ namespace IPA.Config
}
catch (Exception e)
{
- Logger.config.Error($"Error waiting for in-memory updates");
- Logger.config.Error(e);
+ Logger.Config.Error($"Error waiting for in-memory updates");
+ Logger.Config.Error(e);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
diff --git a/IPA.Loader/Config/Data/List.cs b/IPA.Loader/Config/Data/List.cs
index d4f48397..764c619b 100644
--- a/IPA.Loader/Config/Data/List.cs
+++ b/IPA.Loader/Config/Data/List.cs
@@ -1,4 +1,5 @@
-using IPA.Utilities;
+#nullable enable
+using IPA.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -10,9 +11,9 @@ namespace IPA.Config.Data
/// A list of s for serialization by an .
/// Use or to create.
///
- public sealed class List : Value, IList
+ public sealed class List : Value, IList
{
- private readonly List values = new List();
+ private readonly List values = new();
internal List() { }
@@ -22,7 +23,7 @@ namespace IPA.Config.Data
/// the index to retrieve the at
/// the at
///
- public Value this[int index] { get => values[index]; set => values[index] = value; }
+ public Value? this[int index] { get => values[index]; set => values[index] = value; }
///
/// Gets the number of elements in the .
@@ -30,21 +31,22 @@ namespace IPA.Config.Data
///
public int Count => values.Count;
- bool ICollection.IsReadOnly => ((IList)values).IsReadOnly;
+ bool ICollection.IsReadOnly => ((IList)values).IsReadOnly;
///
/// Adds a to the end of this .
///
/// the to add
///
- public void Add(Value item) => values.Add(item);
+ public void Add(Value? item) => values.Add(item);
///
/// Adds a range of s to the end of this .
///
/// the range of s to add
- public void AddRange(IEnumerable vals)
+ public void AddRange(IEnumerable vals)
{
+ if (vals is null) throw new ArgumentNullException(nameof(vals));
foreach (var val in vals) Add(val);
}
@@ -60,7 +62,7 @@ namespace IPA.Config.Data
/// the to check for
/// if the item was founc, otherwise
///
- public bool Contains(Value item) => values.Contains(item);
+ public bool Contains(Value? item) => values.Contains(item);
///
/// Copies the s in the to the in .
@@ -68,14 +70,14 @@ namespace IPA.Config.Data
/// the to copy to
/// the starting index to copy to
///
- public void CopyTo(Value[] array, int arrayIndex) => values.CopyTo(array, arrayIndex);
+ public void CopyTo(Value?[] array, int arrayIndex) => values.CopyTo(array, arrayIndex);
///
/// Gets an enumerator to enumerate the .
///
/// an for this
///
- public IEnumerator GetEnumerator() => ((IList)values).GetEnumerator();
+ public IEnumerator GetEnumerator() => ((IList)values).GetEnumerator();
///
/// Gets the index that a given is in the .
@@ -83,7 +85,7 @@ namespace IPA.Config.Data
/// the to search for
/// the index that the was at, or -1.
///
- public int IndexOf(Value item) => values.IndexOf(item);
+ public int IndexOf(Value? item) => values.IndexOf(item);
///
/// Inserts a at an index.
@@ -91,7 +93,7 @@ namespace IPA.Config.Data
/// the index to insert at
/// the to insert
///
- public void Insert(int index, Value item) => values.Insert(index, item);
+ public void Insert(int index, Value? item) => values.Insert(index, item);
///
/// Removes a from the .
@@ -99,7 +101,7 @@ namespace IPA.Config.Data
/// the to remove
/// if the item was removed, otherwise
///
- public bool Remove(Value item) => values.Remove(item);
+ public bool Remove(Value? item) => values.Remove(item);
///
/// Removes a at an index.
@@ -115,7 +117,7 @@ namespace IPA.Config.Data
public override string ToString()
=> $"[{string.Join(",",this.Select(v => v?.ToString() ?? "null").StrJP())}]";
- IEnumerator IEnumerable.GetEnumerator() => ((IList)values).GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
diff --git a/IPA.Loader/Config/Data/Map.cs b/IPA.Loader/Config/Data/Map.cs
index 21be20b4..8ed8f17c 100644
--- a/IPA.Loader/Config/Data/Map.cs
+++ b/IPA.Loader/Config/Data/Map.cs
@@ -1,4 +1,5 @@
-using IPA.Utilities;
+#nullable enable
+using IPA.Utilities;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -10,10 +11,10 @@ namespace IPA.Config.Data
/// A ordered map of to for serialization by an .
/// Use or to create.
///
- public sealed class Map : Value, IDictionary
+ public sealed class Map : Value, IDictionary
{
- private readonly Dictionary values = new Dictionary();
- private readonly List keyOrder = new List();
+ private readonly Dictionary values = new();
+ private readonly List keyOrder = new();
internal Map() { }
@@ -23,7 +24,7 @@ namespace IPA.Config.Data
/// the key to get the value associated with
/// the value associated with the
///
- public Value this[string key] { get => values[key]; set => values[key] = value; }
+ public Value? this[string key] { get => values[key]; set => values[key] = value; }
///
/// Gets a collection of the keys for the .
@@ -39,7 +40,7 @@ namespace IPA.Config.Data
/// guarantee that order is maintained.
///
///
- public ICollection Values => values.Values;
+ public ICollection Values => values.Values;
///
/// Gets the number of key-value pairs in this .
@@ -47,7 +48,7 @@ namespace IPA.Config.Data
///
public int Count => values.Count;
- bool ICollection>.IsReadOnly => ((IDictionary)values).IsReadOnly;
+ bool ICollection>.IsReadOnly => ((IDictionary)values).IsReadOnly;
///
/// Adds a new with a given key.
@@ -55,13 +56,13 @@ namespace IPA.Config.Data
/// the key to put the value at
/// the to add
///
- public void Add(string key, Value value)
+ public void Add(string key, Value? value)
{
values.Add(key, value);
keyOrder.Add(key);
}
- void ICollection>.Add(KeyValuePair item)
+ void ICollection>.Add(KeyValuePair item)
=> Add(item.Key, item.Value);
///
@@ -74,8 +75,8 @@ namespace IPA.Config.Data
keyOrder.Clear();
}
- bool ICollection>.Contains(KeyValuePair item)
- => ((IDictionary)values).Contains(item);
+ bool ICollection>.Contains(KeyValuePair item)
+ => ((IDictionary)values).Contains(item);
///
/// Checks if the contains a given .
@@ -85,18 +86,18 @@ namespace IPA.Config.Data
///
public bool ContainsKey(string key) => values.ContainsKey(key);
- void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
- => ((IDictionary)values).CopyTo(array, arrayIndex);
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ => ((IDictionary)values).CopyTo(array, arrayIndex);
///
/// Enumerates the 's key-value pairs.
///
/// an of key-value pairs in this
///
- public IEnumerator> GetEnumerator()
+ public IEnumerator> GetEnumerator()
{
foreach (var key in keyOrder)
- yield return new KeyValuePair(key, this[key]);
+ yield return new KeyValuePair(key, this[key]);
}
///
@@ -107,8 +108,8 @@ namespace IPA.Config.Data
///
public bool Remove(string key) => values.Remove(key) && keyOrder.Remove(key);
- bool ICollection>.Remove(KeyValuePair item)
- => ((IDictionary)values).Remove(item) && (keyOrder.Remove(item.Key) || true);
+ bool ICollection>.Remove(KeyValuePair item)
+ => ((IDictionary)values).Remove(item) && (keyOrder.Remove(item.Key) || true);
///
/// Gets the value associated with the specified key.
@@ -117,7 +118,7 @@ namespace IPA.Config.Data
/// the target location of the retrieved object
/// if the key was found and set, otherwise
///
- public bool TryGetValue(string key, out Value value) => values.TryGetValue(key, out value);
+ public bool TryGetValue(string key, out Value? value) => values.TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
diff --git a/IPA.Loader/Config/Data/Primitives.cs b/IPA.Loader/Config/Data/Primitives.cs
index c03b3c90..5abc6d88 100644
--- a/IPA.Loader/Config/Data/Primitives.cs
+++ b/IPA.Loader/Config/Data/Primitives.cs
@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -12,10 +13,28 @@ namespace IPA.Config.Data
///
public sealed class Text : Value
{
+ ///
+ /// Constructs an empty object.
+ ///
+ [Obsolete("Use the String constructor.")]
+ public Text()
+ {
+ Value = null!;
+ }
+
+ ///
+ /// Constructs a object containing the provided value.
+ ///
+ /// The value to construct with.
+ public Text(string value)
+ {
+ Value = value;
+ }
+
///
/// The actual value of this object.
///
- public string Value { get; set; }
+ public string Value { get; init; }
///
/// Converts this into a human-readable format.
@@ -30,6 +49,24 @@ namespace IPA.Config.Data
///
public sealed class Integer : Value
{
+ ///
+ /// Constructs an empty object.
+ ///
+ [Obsolete("Use the long constructor.")]
+ public Integer()
+ {
+ Value = 0;
+ }
+
+ ///
+ /// Constructs a object containing the provided value.
+ ///
+ /// The value to construct with.
+ public Integer(long value)
+ {
+ Value = value;
+ }
+
///
/// The actual value of the object.
///
@@ -50,10 +87,28 @@ namespace IPA.Config.Data
///
/// A representing a floating point value. This may hold a
- /// 's worth of data.
+ /// 's worth of data.
///
public sealed class FloatingPoint : Value
{
+ ///
+ /// Constructs an empty object.
+ ///
+ [Obsolete("Use the long constructor.")]
+ public FloatingPoint()
+ {
+ Value = 0;
+ }
+
+ ///
+ /// Constructs a object containing the provided value.
+ ///
+ /// The value to construct with.
+ public FloatingPoint(decimal value)
+ {
+ Value = value;
+ }
+
///
/// The actual value fo this object.
///
@@ -76,16 +131,37 @@ namespace IPA.Config.Data
/// A representing a boolean value.
///
public sealed class Boolean : Value
- {
+ {
+ ///
+ /// Constructs an empty object.
+ ///
+ [Obsolete("Use the long constructor.")]
+ public Boolean()
+ {
+ Value = false;
+ }
+
+ ///
+ /// Constructs a object containing the provided value.
+ ///
+ /// The value to construct with.
+ public Boolean(bool value)
+ {
+ Value = value;
+ }
+
///
/// The actual value fo this object.
///
- public bool Value { get; set; }
-
+ public bool Value { get; set; }
+
+
///
/// Converts this into a human-readable format.
///
/// the result of Value.ToString().ToLower()
- public override string ToString() => Value.ToString().ToLower();
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase",
+ Justification = "ToLower is the desired display value.")]
+ public override string ToString() => Value.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture);
}
}
diff --git a/IPA.Loader/Config/Data/Value.cs b/IPA.Loader/Config/Data/Value.cs
index 2e0e4eb7..966f844c 100644
--- a/IPA.Loader/Config/Data/Value.cs
+++ b/IPA.Loader/Config/Data/Value.cs
@@ -1,145 +1,146 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace IPA.Config.Data
-{
- ///
- /// A base value type for config data abstract representations, to be serialized with an
- /// . If a is , then
- /// that represents just that: a null in whatever serialization is being used.
- /// Also contains factory functions for all derived types.
- ///
- public abstract class Value
- {
- ///
- /// Converts this into a human-readable format.
- ///
- /// a human-readable string containing the value provided
- public abstract override string ToString();
-
- ///
- /// Creates a Null .
- ///
- ///
- public static Value Null() => null;
-
- ///
- /// Creates an empty .
- ///
- /// an empty
- ///
- public static List List() => new List();
- ///
- /// Creates an empty .
- ///
- /// an empty
- ///
- ///
- public static Map Map() => new Map();
-
- ///
- /// Creates a new representing a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Text From(string val) => Text(val);
- ///
- /// Creates a new object wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Text Text(string val) => val == null ? null : new Text { Value = val };
-
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Integer From(long val) => Integer(val);
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Integer Integer(long val) => new Integer { Value = val };
-
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static FloatingPoint From(decimal val) => Float(val);
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static FloatingPoint Float(decimal val) => new FloatingPoint { Value = val };
-
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Boolean From(bool val) => Bool(val);
- ///
- /// Creates a new wrapping a .
- ///
- /// the value to wrap
- /// a wrapping
- ///
- public static Boolean Bool(bool val) => new Boolean { Value = val };
-
- ///
- /// Creates a new holding the content of an
- /// of .
- ///
- /// the s to initialize the with
- /// a containing the content of
- ///
- public static List From(IEnumerable vals)
- {
- if (vals == null) return null;
- var l = List();
- l.AddRange(vals);
- return l;
- }
-
- ///
- /// Creates a new holding the content of an
- /// of to .
- ///
- /// the dictionary of s to initialize the wtih
- /// a containing the content of
- ///
- ///
- public static Map From(IDictionary vals) => From(vals as IEnumerable>);
-
- ///
- /// Creates a new holding the content of an
- /// of of to .
- ///
- /// the enumerable of of name to
- /// a containing the content of
- ///
- ///
- public static Map From(IEnumerable> vals)
- {
- if (vals == null) return null;
- var m = Map();
- foreach (var v in vals) m.Add(v.Key, v.Value);
- return m;
- }
- }
-}
+#nullable enable
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace IPA.Config.Data
+{
+ ///
+ /// A base value type for config data abstract representations, to be serialized with an
+ /// . If a is , then
+ /// that represents just that: a null in whatever serialization is being used.
+ /// Also contains factory functions for all derived types.
+ ///
+ public abstract class Value
+ {
+ ///
+ /// Converts this into a human-readable format.
+ ///
+ /// a human-readable string containing the value provided
+ public abstract override string ToString();
+
+ ///
+ /// Creates a Null .
+ ///
+ ///
+ public static Value? Null() => null;
+
+ ///
+ /// Creates an empty .
+ ///
+ /// an empty
+ ///
+ public static List List() => new();
+ ///
+ /// Creates an empty .
+ ///
+ /// an empty
+ ///
+ ///
+ public static Map Map() => new();
+
+ ///
+ /// Creates a new representing a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ [return: NotNullIfNotNull("val")]
+ public static Text? From(string? val) => Text(val);
+ ///
+ /// Creates a new object wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ [return: NotNullIfNotNull("val")]
+ public static Text? Text(string? val) => val == null ? null : new(val);
+
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static Integer From(long val) => Integer(val);
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static Integer Integer(long val) => new(val);
+
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static FloatingPoint From(decimal val) => Float(val);
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static FloatingPoint Float(decimal val) => new(val);
+
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static Boolean From(bool val) => Bool(val);
+ ///
+ /// Creates a new wrapping a .
+ ///
+ /// the value to wrap
+ /// a wrapping
+ ///
+ public static Boolean Bool(bool val) => new(val);
+
+ ///
+ /// Creates a new holding the content of an
+ /// of .
+ ///
+ /// the s to initialize the with
+ /// a containing the content of
+ ///
+ [return: NotNullIfNotNull("vals")]
+ public static List? From(IEnumerable? vals)
+ {
+ if (vals is null) return null;
+ var l = List();
+ l.AddRange(vals);
+ return l;
+ }
+
+ ///
+ /// Creates a new holding the content of an
+ /// of to .
+ ///
+ /// the dictionary of s to initialize the wtih
+ /// a containing the content of
+ ///
+ ///
+ public static Map From(IDictionary vals) => From(vals as IEnumerable>);
+
+ ///
+ /// Creates a new holding the content of an
+ /// of of to .
+ ///
+ /// the enumerable of of name to
+ /// a containing the content of
+ ///
+ ///
+ [return: NotNullIfNotNull("vals")]
+ public static Map? From(IEnumerable>? vals)
+ {
+ if (vals is null) return null;
+ var m = Map();
+ foreach (var v in vals) m.Add(v.Key, v.Value);
+ return m;
+ }
+ }
+}
diff --git a/IPA.Loader/Config/Providers/JsonConfigProvider.cs b/IPA.Loader/Config/Providers/JsonConfigProvider.cs
index 250918cc..dd9c8dfb 100644
--- a/IPA.Loader/Config/Providers/JsonConfigProvider.cs
+++ b/IPA.Loader/Config/Providers/JsonConfigProvider.cs
@@ -39,8 +39,8 @@ namespace IPA.Config.Providers
}
catch (Exception e)
{
- Logger.config.Error($"Error reading JSON file {file.FullName}; ignoring");
- Logger.config.Error(e);
+ Logger.Config.Error($"Error reading JSON file {file.FullName}; ignoring");
+ Logger.Config.Error(e);
return Value.Null();
}
}
@@ -54,19 +54,19 @@ namespace IPA.Config.Providers
case JTokenType.Raw: // idk if the parser will normally emit a Raw type, but just to be safe
return VisitToValue(JToken.Parse((tok as JRaw).Value as string));
case JTokenType.Undefined:
- Logger.config.Warn("Found JTokenType.Undefined");
+ Logger.Config.Warn("Found JTokenType.Undefined");
goto case JTokenType.Null;
case JTokenType.Bytes: // never used by Newtonsoft
- Logger.config.Warn("Found JTokenType.Bytes");
+ Logger.Config.Warn("Found JTokenType.Bytes");
goto case JTokenType.Null;
case JTokenType.Comment: // never used by Newtonsoft
- Logger.config.Warn("Found JTokenType.Comment");
+ Logger.Config.Warn("Found JTokenType.Comment");
goto case JTokenType.Null;
case JTokenType.Constructor: // never used by Newtonsoft
- Logger.config.Warn("Found JTokenType.Constructor");
+ Logger.Config.Warn("Found JTokenType.Constructor");
goto case JTokenType.Null;
case JTokenType.Property: // never used by Newtonsoft
- Logger.config.Warn("Found JTokenType.Property");
+ Logger.Config.Warn("Found JTokenType.Property");
goto case JTokenType.Null;
case JTokenType.Null:
return Value.Null();
@@ -133,8 +133,8 @@ namespace IPA.Config.Providers
}
catch (Exception e)
{
- Logger.config.Error($"Error serializing value for {file.FullName}");
- Logger.config.Error(e);
+ Logger.Config.Error($"Error serializing value for {file.FullName}");
+ Logger.Config.Error(e);
}
}
diff --git a/IPA.Loader/Config/SelfConfig.cs b/IPA.Loader/Config/SelfConfig.cs
index 4ad33bc6..abe30bea 100644
--- a/IPA.Loader/Config/SelfConfig.cs
+++ b/IPA.Loader/Config/SelfConfig.cs
@@ -36,7 +36,7 @@ namespace IPA.Config
protected internal virtual void Changed()
{
- Logger.log.Debug("SelfConfig Changed called");
+ Logger.Default.Debug("SelfConfig Changed called");
}
public static void ReadCommandLine(string[] args)
@@ -53,6 +53,12 @@ namespace IPA.Config
case "--no-yeet":
CommandLineValues.YeetMods = false;
break;
+ case "--no-logs":
+ CommandLineValues.WriteLogs = false;
+ break;
+ case "--darken-message":
+ CommandLineValues.Debug.DarkenMessages = true;
+ break;
case "--condense-logs":
CommandLineValues.Debug.CondenseModLogs = true;
break;
@@ -72,13 +78,24 @@ namespace IPA.Config
}
}
+ public void CheckVersionBoundary()
+ {
+ if (ResetGameAssebliesOnVersionChange && Utilities.UnityGame.IsGameVersionBoundary)
+ {
+ GameAssemblies = GetDefaultGameAssemblies();
+ }
+ }
+
internal const string IPAName = "Beat Saber IPA";
- internal const string IPAVersion = "4.1.7.0";
+ internal const string IPAVersion = "4.2.2.0";
// uses Updates.AutoUpdate, Updates.AutoCheckUpdates, YeetMods, Debug.ShowCallSource, Debug.ShowDebug,
// Debug.CondenseModLogs
internal static SelfConfig CommandLineValues = new();
+ // For readability's sake, I want the default values to be visible in source.
+#pragma warning disable CA1805 // Do not initialize unnecessarily
+
// END: section ignore
public virtual bool Regenerate { get; set; } = true;
@@ -145,30 +162,64 @@ namespace IPA.Config
public virtual bool SyncLogging { get; set; } = false;
// LINE: ignore
public static bool SyncLogging_ => Instance?.Debug?.SyncLogging ?? false;
+
+ public virtual bool DarkenMessages { get; set; } = false;
+ // LINE: ignore 2
+ public static bool DarkenMessages_ => (Instance?.Debug?.DarkenMessages ?? false)
+ || CommandLineValues.Debug.DarkenMessages;
}
// LINE: ignore
[NonNullable]
- public virtual Debug_ Debug { get; set; } = new Debug_();
+ public virtual Debug_ Debug { get; set; } = new();
+
+ public class AntiMalware_
+ {
+ public virtual bool UseIfAvailable { get; set; } = true;
+ // LINE: ignore
+ public static bool UseIfAvailable_ => Instance?.AntiMalware?.UseIfAvailable ?? true;
+
+ public virtual bool RunPartialThreatCode { get; set; } = false;
+ // LINE: ignore
+ public static bool RunPartialThreatCode_ => Instance?.AntiMalware?.RunPartialThreatCode ?? true;
+ }
+
+ // LINE: ignore
+ [NonNullable]
+ public virtual AntiMalware_ AntiMalware { get; set; } = new();
public virtual bool YeetMods { get; set; } = true;
// LINE: ignore 2
public static bool YeetMods_ => (Instance?.YeetMods ?? true)
&& CommandLineValues.YeetMods;
+ [JsonIgnore]
+ public bool WriteLogs { get; set; } = true;
+
+ public virtual bool ResetGameAssebliesOnVersionChange { get; set; } = true;
+
// LINE: ignore
- [NonNullable, UseConverter(typeof(CollectionConverter>))]
- public virtual HashSet GameAssemblies { get; set; } = new HashSet
+ [NonNullable, UseConverter(typeof(CollectionConverter>))]
+ public virtual HashSet GameAssemblies { get; set; } = GetDefaultGameAssemblies();
+
+ // BEGIN: section ignore
+ public static HashSet GetDefaultGameAssemblies()
+ => new()
{
- // LINE: ignore 5
#if BeatSaber // provide these defaults only for Beat Saber builds
"Main.dll", "Core.dll", "HMLib.dll", "HMUI.dll", "HMRendering.dll", "VRUI.dll",
"BeatmapCore.dll", "GameplayCore.dll", "HMLibAttributes.dll", "BeatmapEditor3D.dll"
#else // otherwise specify Assembly-CSharp.dll
"Assembly-CSharp.dll"
- // LINE: ignore
#endif
};
+ // END: section ignore
+
+ // LINE: ignore
+#if false // used to make schema gen happy
+ private static HashSet GetDefaultGameAssemblies() => null;
+ // LINE: ignore
+#endif
// LINE: ignore
public static HashSet GameAssemblies_ => Instance?.GameAssemblies ?? new HashSet { "Assembly-CSharp.dll" };
diff --git a/IPA.Loader/Config/Stores/Attributes.cs b/IPA.Loader/Config/Stores/Attributes.cs
index 574dfd80..66c63e16 100644
--- a/IPA.Loader/Config/Stores/Attributes.cs
+++ b/IPA.Loader/Config/Stores/Attributes.cs
@@ -1,6 +1,8 @@
-using IPA.Config.Stores.Converters;
+#nullable enable
+using IPA.Config.Stores.Converters;
using System;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace IPA.Config.Stores.Attributes
@@ -37,23 +39,25 @@ namespace IPA.Config.Stores.Attributes
///
/// Gets whether or not to use the default converter for the member type instead of the specified type.
///
+ [MemberNotNullWhen(false, nameof(ConverterType))]
public bool UseDefaultConverterForType { get; }
///
/// Gets the type of the converter to use.
///
- public Type ConverterType { get; }
+ public Type? ConverterType { get; }
///
/// Gets the target type of the converter if it is avaliable at instantiation time, otherwise
/// .
///
- public Type ConverterTargetType { get; }
+ public Type? ConverterTargetType { get; }
///
/// Gets whether or not this converter is a generic .
///
- public bool IsGenericConverter => ConverterTargetType != null;
+ [MemberNotNullWhen(true, nameof(ConverterTargetType))]
+ public bool IsGenericConverter => ConverterTargetType is not null;
///
/// Creates a new specifying to use the default converter type for the target member.
@@ -67,11 +71,17 @@ namespace IPA.Config.Stores.Attributes
/// the type to assign to
public UseConverterAttribute(Type converterType)
{
+ if (converterType is null)
+ throw new ArgumentNullException(nameof(converterType));
+
UseDefaultConverterForType = false;
ConverterType = converterType;
+ if (converterType.IsValueType)
+ throw new ArgumentException("Type is not a value converter!");
+
var baseT = ConverterType.BaseType;
- while (baseT != null && baseT != typeof(object) &&
+ while (baseT != typeof(object) &&
(!baseT.IsGenericType || baseT.GetGenericTypeDefinition() != typeof(ValueConverter<>)))
baseT = baseT.BaseType;
if (baseT == typeof(object)) ConverterTargetType = null;
diff --git a/IPA.Loader/Config/Stores/CollectionConverter.cs b/IPA.Loader/Config/Stores/CollectionConverter.cs
index 635ba49c..16093540 100644
--- a/IPA.Loader/Config/Stores/CollectionConverter.cs
+++ b/IPA.Loader/Config/Stores/CollectionConverter.cs
@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -14,7 +15,7 @@ namespace IPA.Config.Stores.Converters
/// the type of the items in the collection
/// the instantiated type of collection
public class CollectionConverter : ValueConverter
- where TCollection : ICollection
+ where TCollection : ICollection
{
///
/// Creates a using the default converter for the
@@ -54,7 +55,7 @@ namespace IPA.Config.Stores.Converters
///
protected void PopulateFromValue(TCollection col, List list, object parent)
{
- //Logger.log.Debug($"CollectionConverter<{typeof(T)}, {typeof(TCollection)}>({BaseConverter.GetType()}).PopulateFromValue([object], {list}, {parent.GetType()})");
+ if (list is null) throw new ArgumentNullException(nameof(list));
foreach (var it in list)
col.Add(BaseConverter.FromValue(it, parent));
}
@@ -66,9 +67,9 @@ namespace IPA.Config.Stores.Converters
/// the object that will own the resulting
/// a new holding the deserialized content of
///
- public override TCollection FromValue(Value value, object parent)
+ public override TCollection FromValue(Value? value, object parent)
{
- if (!(value is List list)) throw new ArgumentException("Argument not a List", nameof(value));
+ if (value is not List list) throw new ArgumentException("Argument not a List", nameof(value));
var col = Create(list.Count, parent);
PopulateFromValue(col, list, parent);
@@ -81,7 +82,7 @@ namespace IPA.Config.Stores.Converters
/// the object owning
/// the that was serialized into
///
- public override Value ToValue(TCollection obj, object parent)
+ public override Value? ToValue(TCollection? obj, object parent)
=> Value.From(obj.Select(t => BaseConverter.ToValue(t, parent)));
}
///
@@ -92,7 +93,7 @@ namespace IPA.Config.Stores.Converters
/// the type of the converter to use for
///
public sealed class CollectionConverter : CollectionConverter
- where TCollection : ICollection
+ where TCollection : ICollection
where TConverter : ValueConverter, new()
{
///
@@ -110,7 +111,7 @@ namespace IPA.Config.Stores.Converters
///
/// the element type of the
///
- public class ISetConverter : CollectionConverter>
+ public class ISetConverter : CollectionConverter>
{
///
/// Creates an using the default converter for .
@@ -128,8 +129,8 @@ namespace IPA.Config.Stores.Converters
/// the size to initialize it to
/// the object that will own the new object
/// the new
- protected override ISet Create(int size, object parent)
- => new HashSet();
+ protected override ISet Create(int size, object parent)
+ => new HashSet();
}
///
/// An which default constructs a converter for use as the value converter.
@@ -155,7 +156,7 @@ namespace IPA.Config.Stores.Converters
///
/// the element type of the
///
- public class ListConverter : CollectionConverter>
+ public class ListConverter : CollectionConverter>
{
///
/// Creates an using the default converter for .
@@ -173,8 +174,8 @@ namespace IPA.Config.Stores.Converters
/// the size to initialize it to
/// the object that will own the new object
/// the new
- protected override List Create(int size, object parent)
- => new List(size);
+ protected override List Create(int size, object parent)
+ => new(size);
}
///
/// A which default constructs a converter for use as the value converter.
@@ -199,7 +200,7 @@ namespace IPA.Config.Stores.Converters
///
/// the element type of the
///
- public class IListConverter : CollectionConverter>
+ public class IListConverter : CollectionConverter>
{
///
/// Creates an using the default converter for .
@@ -217,8 +218,8 @@ namespace IPA.Config.Stores.Converters
/// the size to initialize it to
/// the object that will own the new object
/// the new
- protected override IList Create(int size, object parent)
- => new List(size);
+ protected override IList Create(int size, object parent)
+ => new List(size);
}
///
/// An which default constructs a converter for use as the value converter.
diff --git a/IPA.Loader/Config/Stores/Converters.cs b/IPA.Loader/Config/Stores/Converters.cs
index 7821b90a..433640ad 100644
--- a/IPA.Loader/Config/Stores/Converters.cs
+++ b/IPA.Loader/Config/Stores/Converters.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
@@ -22,7 +23,7 @@ namespace IPA.Config.Stores.Converters
///
/// the to get the integral value of
/// the integral value of , or
- public static long? IntValue(Value val)
+ public static long? IntValue(Value? val)
=> val is Integer inte ? inte.Value :
val is FloatingPoint fp ? fp.AsInteger()?.Value :
null;
@@ -32,7 +33,7 @@ namespace IPA.Config.Stores.Converters
///
/// the to get the floaing point value of
/// the floaing point value of , or
- public static decimal? FloatValue(Value val)
+ public static decimal? FloatValue(Value? val)
=> val is FloatingPoint fp ? fp.Value :
val is Integer inte ? inte.AsFloat()?.Value :
null;
@@ -79,11 +80,11 @@ namespace IPA.Config.Stores.Converters
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
{ // this is a Nullable
//Logger.log.Debug($"gives NullableConverter<{Nullable.GetUnderlyingType(t)}>");
- return (typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t)));
+ return typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t));
}
//Logger.log.Debug($"gives converter for value type {t}");
- var valConv = Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t)) as IValConv;
+ var valConv = (IValConv)Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t));
return valConv.Get();
}
@@ -117,7 +118,7 @@ namespace IPA.Config.Stores.Converters
IValConv, IValConv,
IValConv
{
- internal static readonly ValConvImpls Impl = new ValConvImpls();
+ internal static readonly ValConvImpls Impl = new();
Type IValConv.Get() => typeof(CharConverter);
Type IValConv.Get() => typeof(LongConverter);
Type IValConv.Get() => typeof(ULongConverter);
@@ -145,7 +146,7 @@ namespace IPA.Config.Stores.Converters
/// the type of the that this works on
public static class Converter
{
- private static ValueConverter defaultConverter = null;
+ private static ValueConverter? defaultConverter;
///
/// Gets the default for the current type.
///
@@ -158,7 +159,7 @@ namespace IPA.Config.Stores.Converters
//Logger.log.Debug($"Converter<{t}>.MakeDefault()");
static ValueConverter MakeInstOf(Type ty)
- => Activator.CreateInstance(ty) as ValueConverter;
+ => (ValueConverter)Activator.CreateInstance(ty);
return MakeInstOf(Converter.GetDefaultConverterType(t));
}
@@ -193,16 +194,16 @@ namespace IPA.Config.Stores.Converters
/// the tree to convert
/// the object which will own the created object
/// the object represented by
- public override T? FromValue(Value value, object parent)
- => value == null ? null : new T?(baseConverter.FromValue(value, parent));
+ public override T? FromValue(Value? value, object parent)
+ => value is null ? null : new T?(baseConverter.FromValue(value, parent));
///
/// Converts a nullable to a tree.
///
/// the value to serialize
/// the object which owns
/// a tree representing .
- public override Value ToValue(T? obj, object parent)
- => obj == null ? null : baseConverter.ToValue(obj.Value, parent);
+ public override Value? ToValue(T? obj, object parent)
+ => obj is null ? null : baseConverter.ToValue(obj.Value, parent);
}
///
@@ -237,7 +238,7 @@ namespace IPA.Config.Stores.Converters
/// the object which will own the created object
/// the deserialized enum value
/// if is not a node
- public override T FromValue(Value value, object parent)
+ public override T FromValue(Value? value, object parent)
=> value is Text t
? (T)Enum.Parse(typeof(T), t.Value)
: throw new ArgumentException("Value not a string", nameof(value));
@@ -248,8 +249,8 @@ namespace IPA.Config.Stores.Converters
/// the value to serialize
/// the object which owns
/// a node representing
- public override Value ToValue(T obj, object parent)
- => Value.Text(obj.ToString());
+ public override Value? ToValue(T? obj, object parent)
+ => Value.Text(obj?.ToString());
}
///
@@ -267,7 +268,7 @@ namespace IPA.Config.Stores.Converters
/// the object which will own the created object
/// the deserialized enum value
/// if is not a node
- public override T FromValue(Value value, object parent)
+ public override T FromValue(Value? value, object parent)
=> value is Text t
? (T)Enum.Parse(typeof(T), t.Value, true)
: throw new ArgumentException("Value not a string", nameof(value));
@@ -278,8 +279,8 @@ namespace IPA.Config.Stores.Converters
/// the value to serialize
/// the object which owns
/// a node representing
- public override Value ToValue(T obj, object parent)
- => Value.Text(obj.ToString());
+ public override Value? ToValue(T? obj, object parent)
+ => Value.Text(obj?.ToString());
}
///
@@ -296,7 +297,7 @@ namespace IPA.Config.Stores.Converters
/// the object which will own the created object
/// the deserialized enum value
/// if is not a numeric node
- public override T FromValue(Value value, object parent)
+ public override T FromValue(Value? value, object parent)
=> (T)Enum.ToObject(typeof(T), Converter.IntValue(value)
?? throw new ArgumentException("Value not a numeric node", nameof(value)));
@@ -306,7 +307,7 @@ namespace IPA.Config.Stores.Converters
/// the value to serialize
/// the object which owns
/// an node representing
- public override Value ToValue(T obj, object parent)
+ public override Value ToValue(T? obj, object parent)
=> Value.Integer(Convert.ToInt64(obj));
}
@@ -314,7 +315,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of .
///
/// the value type of the dictionary
- public class IDictionaryConverter : ValueConverter>
+ public class IDictionaryConverter : ValueConverter>
{
///
/// Gets the converter for the dictionary's value type.
@@ -338,9 +339,9 @@ namespace IPA.Config.Stores.Converters
/// the to convert
/// the parent that will own the resulting object
/// the deserialized dictionary
- public override IDictionary FromValue(Value value, object parent)
- => (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
- ?.ToDictionary(p => p.Key, p => p.val)
+ public override IDictionary FromValue(Value? value, object parent)
+ => ((value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
+ ?.ToDictionary(p => p.Key, p => p.val))
?? throw new ArgumentException("Value not a map", nameof(value));
///
@@ -349,8 +350,8 @@ namespace IPA.Config.Stores.Converters
/// the dictionary to serialize
/// the object that owns the dictionary
/// the dictionary serialized as a
- public override Value ToValue(IDictionary obj, object parent)
- => Value.From(obj.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
+ public override Value? ToValue(IDictionary? obj, object parent)
+ => Value.From(obj.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
///
@@ -373,7 +374,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of .
///
/// the value type of the dictionary
- public class DictionaryConverter : ValueConverter>
+ public class DictionaryConverter : ValueConverter>
{
///
/// Gets the converter for the dictionary's value type.
@@ -397,7 +398,7 @@ namespace IPA.Config.Stores.Converters
/// the to convert
/// the parent that will own the resulting object
/// the deserialized dictionary
- public override Dictionary FromValue(Value value, object parent)
+ public override Dictionary FromValue(Value? value, object parent)
=> (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val)
?? throw new ArgumentException("Value not a map", nameof(value));
@@ -408,8 +409,8 @@ namespace IPA.Config.Stores.Converters
/// the dictionary to serialize
/// the object that owns the dictionary
/// the dictionary serialized as a
- public override Value ToValue(Dictionary obj, object parent)
- => Value.From(obj.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
+ public override Value? ToValue(Dictionary? obj, object parent)
+ => Value.From(obj?.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
///
@@ -433,7 +434,7 @@ namespace IPA.Config.Stores.Converters
/// A converter for instances of .
///
/// the value type of the dictionary
- public class IReadOnlyDictionaryConverter : ValueConverter>
+ public class IReadOnlyDictionaryConverter : ValueConverter>
{
///
/// Gets the converter for the dictionary's value type.
@@ -457,7 +458,7 @@ namespace IPA.Config.Stores.Converters
/// the to convert
/// the parent that will own the resulting object
/// the deserialized dictionary
- public override IReadOnlyDictionary FromValue(Value value, object parent)
+ public override IReadOnlyDictionary FromValue(Value? value, object parent)
=> (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent)))
?.ToDictionary(p => p.Key, p => p.val)
?? throw new ArgumentException("Value not a map", nameof(value));
@@ -468,8 +469,8 @@ namespace IPA.Config.Stores.Converters
/// the dictionary to serialize
/// the object that owns the dictionary
/// the dictionary serialized as a
- public override Value ToValue(IReadOnlyDictionary obj, object parent)
- => Value.From(obj.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
+ public override Value? ToValue(IReadOnlyDictionary? obj, object parent)
+ => Value.From(obj?.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent))));
}
///
@@ -500,7 +501,7 @@ namespace IPA.Config.Stores.Converters
/// the object which will own the created object
/// the deserialized Color object
/// if is not a node or couldn't be parsed into a Color object
- public override Color FromValue(Value value, object parent)
+ public override Color FromValue(Value? value, object parent)
{
if (value is Text t)
{
@@ -526,146 +527,146 @@ namespace IPA.Config.Stores.Converters
internal class StringConverter : ValueConverter
{
- public override string FromValue(Value value, object parent)
+ public override string? FromValue(Value? value, object parent)
=> (value as Text)?.Value;
- public override Value ToValue(string obj, object parent)
+ public override Value? ToValue(string? obj, object parent)
=> Value.From(obj);
}
internal class CharConverter : ValueConverter
{
- public override char FromValue(Value value, object parent)
+ public override char FromValue(Value? value, object parent)
=> (value as Text)?.Value[0]
?? throw new ArgumentException("Value not a text node", nameof(value)); // can throw nullptr
- public override Value ToValue(char obj, object parent)
+ public override Value? ToValue(char obj, object parent)
=> Value.From(char.ToString(obj));
}
internal class LongConverter : ValueConverter
{
- public override long FromValue(Value value, object parent)
+ public override long FromValue(Value? value, object parent)
=> Converter.IntValue(value)
?? throw new ArgumentException("Value not a numeric value", nameof(value));
- public override Value ToValue(long obj, object parent)
+ public override Value? ToValue(long obj, object parent)
=> Value.From(obj);
}
internal class ULongConverter : ValueConverter
{
- public override ulong FromValue(Value value, object parent)
+ public override ulong FromValue(Value? value, object parent)
=> (ulong)(Converter.FloatValue(value)
?? throw new ArgumentException("Value not a numeric value", nameof(value)));
- public override Value ToValue(ulong obj, object parent)
+ public override Value? ToValue(ulong obj, object parent)
=> Value.From(obj);
}
internal class IntPtrConverter : ValueConverter
{
- public override IntPtr FromValue(Value value, object parent)
+ public override IntPtr FromValue(Value? value, object parent)
=> (IntPtr)Converter.Default.FromValue(value, parent);
- public override Value ToValue(IntPtr obj, object parent)
+ public override Value? ToValue(IntPtr obj, object parent)
=> Value.From((long)obj);
}
internal class UIntPtrConverter : ValueConverter
{
- public override UIntPtr FromValue(Value value, object parent)
+ public override UIntPtr FromValue(Value? value, object parent)
=> (UIntPtr)Converter.Default.FromValue(value, parent);
- public override Value ToValue(UIntPtr obj, object parent)
+ public override Value? ToValue(UIntPtr obj, object parent)
=> Value.From((decimal)obj);
}
internal class IntConverter : ValueConverter
{
- public override int FromValue(Value value, object parent)
+ public override int FromValue(Value? value, object parent)
=> (int)Converter.Default.FromValue(value, parent);
- public override Value ToValue(int obj, object parent)
+ public override Value? ToValue(int obj, object parent)
=> Value.From(obj);
}
internal class UIntConverter : ValueConverter
{
- public override uint FromValue(Value value, object parent)
+ public override uint FromValue(Value? value, object parent)
=> (uint)Converter.Default.FromValue(value, parent);
- public override Value ToValue(uint obj, object parent)
+ public override Value? ToValue(uint obj, object parent)
=> Value.From(obj);
}
internal class ShortConverter : ValueConverter
{
- public override short FromValue(Value value, object parent)
+ public override short FromValue(Value? value, object parent)
=> (short)Converter.Default.FromValue(value, parent);
- public override Value ToValue(short obj, object parent)
+ public override Value? ToValue(short obj, object parent)
=> Value.From(obj);
}
internal class UShortConverter : ValueConverter
{
- public override ushort FromValue(Value value, object parent)
+ public override ushort FromValue(Value? value, object parent)
=> (ushort)Converter.Default.FromValue(value, parent);
- public override Value ToValue(ushort obj, object parent)
+ public override Value? ToValue(ushort obj, object parent)
=> Value.From(obj);
}
internal class ByteConverter : ValueConverter
{
- public override byte FromValue(Value value, object parent)
+ public override byte FromValue(Value? value, object parent)
=> (byte)Converter.Default.FromValue(value, parent);
- public override Value ToValue(byte obj, object parent)
+ public override Value? ToValue(byte obj, object parent)
=> Value.From(obj);
}
internal class SByteConverter : ValueConverter
{
- public override sbyte FromValue(Value value, object parent)
+ public override sbyte FromValue(Value? value, object parent)
=> (sbyte)Converter.Default.FromValue(value, parent);
- public override Value ToValue(sbyte obj, object parent)
+ public override Value? ToValue(sbyte obj, object parent)
=> Value.From(obj);
}
internal class DecimalConverter : ValueConverter
{
- public override decimal FromValue(Value value, object parent)
+ public override decimal FromValue(Value? value, object parent)
=> Converter.FloatValue(value) ?? throw new ArgumentException("Value not a numeric value", nameof(value));
- public override Value ToValue(decimal obj, object parent)
+ public override Value? ToValue(decimal obj, object parent)
=> Value.From(obj);
}
internal class FloatConverter : ValueConverter
{
- public override float FromValue(Value value, object parent)
+ public override float FromValue(Value? value, object parent)
=> (float)Converter.Default.FromValue(value, parent);
- public override Value ToValue(float obj, object parent)
+ public override Value? ToValue(float obj, object parent)
=> Value.From((decimal)obj);
}
internal class DoubleConverter : ValueConverter
{
- public override double FromValue(Value value, object parent)
+ public override double FromValue(Value? value, object parent)
=> (double)Converter.Default.FromValue(value, parent);
- public override Value ToValue(double obj, object parent)
+ public override Value? ToValue(double obj, object parent)
=> Value.From((decimal)obj);
}
internal class BooleanConverter : ValueConverter
{
- public override bool FromValue(Value value, object parent)
+ public override bool FromValue(Value? value, object parent)
=> (value as Boolean)?.Value ?? throw new ArgumentException("Value not a Boolean", nameof(value));
- public override Value ToValue(bool obj, object parent)
+ public override Value? ToValue(bool obj, object parent)
=> Value.From(obj);
}
internal class DateTimeConverter : ValueConverter
{
- public override DateTime FromValue(Value value, object parent)
+ public override DateTime FromValue(Value? value, object parent)
{
- if (!(value is Text text))
+ if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@@ -678,14 +679,14 @@ namespace IPA.Config.Stores.Converters
throw new ArgumentException($"Parsing failed, {text.Value}");
}
- public override Value ToValue(DateTime obj, object parent) => Value.Text(obj.ToString("O"));
+ public override Value? ToValue(DateTime obj, object parent) => Value.Text(obj.ToString("O"));
}
internal class DateTimeOffsetConverter : ValueConverter
{
- public override DateTimeOffset FromValue(Value value, object parent)
+ public override DateTimeOffset FromValue(Value? value, object parent)
{
- if (!(value is Text text))
+ if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@@ -703,9 +704,9 @@ namespace IPA.Config.Stores.Converters
internal class TimeSpanConverter : ValueConverter
{
- public override TimeSpan FromValue(Value value, object parent)
+ public override TimeSpan FromValue(Value? value, object parent)
{
- if (!(value is Text text))
+ if (value is not Text text)
{
throw new ArgumentException("Value is not of type Text", nameof(value));
}
@@ -718,6 +719,6 @@ namespace IPA.Config.Stores.Converters
throw new ArgumentException($"Parsing failed, {text.Value}");
}
- public override Value ToValue(TimeSpan obj, object parent) => Value.Text(obj.ToString());
+ public override Value? ToValue(TimeSpan obj, object parent) => Value.Text(obj.ToString());
}
}
diff --git a/IPA.Loader/Config/Stores/CustomObjectConverter.cs b/IPA.Loader/Config/Stores/CustomObjectConverter.cs
index be881cc7..10787398 100644
--- a/IPA.Loader/Config/Stores/CustomObjectConverter.cs
+++ b/IPA.Loader/Config/Stores/CustomObjectConverter.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using System;
namespace IPA.Config.Stores.Converters
@@ -12,24 +13,26 @@ namespace IPA.Config.Stores.Converters
{
private interface IImpl
{
- T FromValue(Value value, object parent);
- Value ToValue(T obj, object parent);
+ T? FromValue(Value? value, object parent);
+ Value? ToValue(T? obj, object parent);
}
private class Impl : IImpl where U : class, GeneratedStoreImpl.IGeneratedStore, T
{
private static readonly GeneratedStoreImpl.GeneratedStoreCreator creator = GeneratedStoreImpl.GetCreator(typeof(T));
- private static U Create(GeneratedStoreImpl.IGeneratedStore parent)
- => creator(parent) as U;
+ private static U Create(GeneratedStoreImpl.IGeneratedStore? parent)
+ => (U)creator(parent);
- public T FromValue(Value value, object parent)
+ public T? FromValue(Value? value, object parent)
{ // lots of casting here, but it works i promise (probably) (parent can be a non-IGeneratedStore, however it won't necessarily behave then)
+ if (value is null) return null;
var obj = Create(parent as GeneratedStoreImpl.IGeneratedStore);
obj.Deserialize(value);
return obj;
}
- public Value ToValue(T obj, object parent)
+ public Value? ToValue(T? obj, object parent)
{
+ if (obj is null) return null;
if (obj is GeneratedStoreImpl.IGeneratedStore store)
return store.Serialize();
else
@@ -51,7 +54,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that will own the deserialized value
/// the deserialized value
///
- public static T Deserialize(Value value, object parent)
+ public static T? Deserialize(Value? value, object parent)
=> impl.FromValue(value, parent);
///
@@ -61,7 +64,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that owns
/// the tree that represents
///
- public static Value Serialize(T obj, object parent)
+ public static Value? Serialize(T? obj, object parent)
=> impl.ToValue(obj, parent);
///
@@ -71,7 +74,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that will own the deserialized value
/// the deserialized value
///
- public override T FromValue(Value value, object parent)
+ public override T? FromValue(Value? value, object parent)
=> Deserialize(value, parent);
///
@@ -81,7 +84,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that owns
/// the tree that represents
///
- public override Value ToValue(T obj, object parent)
+ public override Value? ToValue(T? obj, object parent)
=> Serialize(obj, parent);
}
@@ -104,7 +107,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that will own the deserialized value
/// the deserialized value
///
- public static T Deserialize(Value value, object parent)
+ public static T Deserialize(Value? value, object parent)
=> deserialize(value, parent);
///
@@ -123,7 +126,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that will own the deserialized value
/// the deserialized value
///
- public override T FromValue(Value value, object parent)
+ public override T FromValue(Value? value, object parent)
=> Deserialize(value, parent);
///
@@ -133,7 +136,7 @@ namespace IPA.Config.Stores.Converters
/// the parent object that owns
/// the tree that represents
///
- public override Value ToValue(T obj, object parent)
+ public override Value? ToValue(T obj, object parent)
=> Serialize(obj);
}
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs
index 1ad9fd12..dc7f5ea9 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,12 +14,12 @@ namespace IPA.Config.Stores
{
internal delegate Value SerializeObject(T obj);
- internal delegate T DeserializeObject(Value val, object parent);
+ internal delegate T DeserializeObject(Value? val, object parent);
private static class DelegateStore
{
- public static SerializeObject Serialize;
- public static DeserializeObject Deserialize;
+ public static SerializeObject? Serialize;
+ public static DeserializeObject? Deserialize;
}
internal static SerializeObject GetSerializerDelegate()
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
index 15d54c97..b59bf7d0 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
index 7f0bdffe..7f8e44f6 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections;
@@ -43,6 +44,7 @@ namespace IPA.Config.Stores
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal,
Action thisarg, Action parentobj)
{
+ if (!member.IsNullable) throw new InvalidOperationException("EmitDeserializeNullable called for non-nullable!");
thisarg ??= il => il.Emit(OpCodes.Ldarg_0);
parentobj ??= thisarg;
EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal, thisarg, parentobj);
@@ -100,7 +102,7 @@ namespace IPA.Config.Stores
var structure = ReadObjectMembers(targetType);
if (!structure.Any())
{
- Logger.config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" +
+ Logger.Config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" +
$" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldloca, resultLocal);
@@ -121,7 +123,7 @@ namespace IPA.Config.Stores
}
else
{
- Logger.config.Warn($"Implicit conversions to {expected} are not currently implemented");
+ Logger.Config.Warn($"Implicit conversions to {expected} are not currently implemented");
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull);
}
@@ -161,6 +163,9 @@ namespace IPA.Config.Stores
private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, LocalAllocator GetLocal,
Action thisobj, Action parentobj)
{
+ if (!member.HasConverter)
+ throw new InvalidOperationException("EmitDeserializeConverter called for member without converter");
+
using var stlocal = GetLocal.Allocate(typeof(Value));
using var valLocal = GetLocal.Allocate(member.Type);
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
index c069905f..7efa987a 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
@@ -36,13 +37,12 @@ namespace IPA.Config.Stores
private static readonly MethodInfo CreateGParent =
typeof(GeneratedStoreImpl).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null,
CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty());
- internal static T Create(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
+ internal static T Create(IGeneratedStore? parent) where T : class => (T)Create(typeof(T), parent);
- private static IConfigStore Create(Type type, IGeneratedStore parent)
+ private static IConfigStore Create(Type type, IGeneratedStore? parent)
=> GetCreator(type)(parent);
- private static readonly SingleCreationValueCache generatedCreators
- = new SingleCreationValueCache();
+ private static readonly SingleCreationValueCache generatedCreators = new();
private static (GeneratedStoreCreator ctor, Type type) GetCreatorAndGeneratedType(Type t)
=> generatedCreators.GetOrAdd(t, MakeCreator);
@@ -55,7 +55,7 @@ namespace IPA.Config.Stores
internal const string GeneratedAssemblyName = "IPA.Config.Generated";
- private static AssemblyBuilder assembly = null;
+ private static AssemblyBuilder? assembly;
private static AssemblyBuilder Assembly
{
get
@@ -75,7 +75,7 @@ namespace IPA.Config.Stores
Assembly.Save(file);
}
- private static ModuleBuilder module = null;
+ private static ModuleBuilder? module;
private static ModuleBuilder Module
{
get
@@ -88,7 +88,7 @@ namespace IPA.Config.Stores
}
// TODO: does this need to be a SingleCreationValueCache or similar?
- private static readonly Dictionary> TypeRequiredConverters = new Dictionary>();
+ private static readonly Dictionary> TypeRequiredConverters = new();
private static void CreateAndInitializeConvertersFor(Type type, IEnumerable structure)
{
if (!TypeRequiredConverters.TryGetValue(type, out var converters))
@@ -96,7 +96,8 @@ namespace IPA.Config.Stores
var converterFieldType = Module.DefineType($"{type.FullName}",
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.AnsiClass); // a static class
- var uniqueConverterTypes = structure.Where(m => m.HasConverter).Select(m => m.Converter).Distinct().ToArray();
+ var uniqueConverterTypes = structure.Where(m => m.HasConverter)
+ .Select(m => m.Converter).NonNull().Distinct().ToArray();
converters = new Dictionary(uniqueConverterTypes.Length);
foreach (var convType in uniqueConverterTypes)
@@ -122,11 +123,14 @@ namespace IPA.Config.Stores
TypeRequiredConverters.Add(type, converters);
- converterFieldType.CreateType();
+ _ = converterFieldType.CreateType();
}
- foreach (var member in structure.Where(m => m.HasConverter))
+ foreach (var member in structure)
+ {
+ if (!member.HasConverter) continue;
member.ConverterField = converters[member.Converter];
+ }
}
}
}
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs
index 350ef742..28620626 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs
@@ -1,4 +1,5 @@
-using IPA.Logging;
+#nullable enable
+using IPA.Logging;
using System;
using System.Collections.Generic;
using System.Reflection;
@@ -42,41 +43,41 @@ namespace IPA.Config.Stores
internal class Impl : IConfigStore
{
private readonly IGeneratedStore generated;
- private long enteredTransactions = 0;
+ private long enteredTransactions;
internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) });
public Impl(IGeneratedStore store) => generated = store;
- private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
+ private readonly AutoResetEvent resetEvent = new(false);
public WaitHandle SyncObject => resetEvent;
- public static WaitHandle ImplGetSyncObject(IGeneratedStore s) => FindImpl(s).SyncObject;
+ public static WaitHandle? ImplGetSyncObject(IGeneratedStore s) => FindImpl(s)?.SyncObject;
internal static MethodInfo ImplGetSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetSyncObject));
- public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim();
- public static ReaderWriterLockSlim ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject;
+ public ReaderWriterLockSlim WriteSyncObject { get; } = new();
+ public static ReaderWriterLockSlim? ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject;
internal static MethodInfo ImplGetWriteSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetWriteSyncObject));
internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
- public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
+ public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s)?.SignalChanged();
public void SignalChanged()
{
try
{
- resetEvent.Set();
+ _ = resetEvent.Set();
}
catch (ObjectDisposedException e)
{
- Logger.config.Error($"ObjectDisposedException while signalling a change for generated store {generated?.GetType()}");
- Logger.config.Error(e);
+ Logger.Config.Error($"ObjectDisposedException while signalling a change for generated store {generated?.GetType()}");
+ Logger.Config.Error(e);
}
}
internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));
- public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s).InvokeChanged();
+ public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s)?.InvokeChanged();
public void InvokeChanged() => generated.Changed();
internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
- public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
+ public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s)?.TakeRead();
public void TakeRead()
{
if (!WriteSyncObject.IsWriteLockHeld)
@@ -84,7 +85,7 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead));
- public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead();
+ public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s)?.ReleaseRead();
public void ReleaseRead()
{
if (!WriteSyncObject.IsWriteLockHeld)
@@ -92,24 +93,24 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite));
- public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite();
+ public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s)?.TakeWrite();
public void TakeWrite() => WriteSyncObject.EnterWriteLock();
internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite));
- public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite();
+ public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s)?.ReleaseWrite();
public void ReleaseWrite() => WriteSyncObject.ExitWriteLock();
internal static MethodInfo ImplChangeTransactionMethod = typeof(Impl).GetMethod(nameof(ImplChangeTransaction));
- public static IDisposable ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s).ChangeTransaction(nest);
+ public static IDisposable? ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s)?.ChangeTransaction(nest);
// TODO: improve trasactionals so they don't always save in every case
public IDisposable ChangeTransaction(IDisposable nest, bool takeWrite = true)
=> GetFreeTransaction().InitWith(this, nest, takeWrite && !WriteSyncObject.IsWriteLockHeld);
- private ChangeTransactionObj GetFreeTransaction()
+ private static ChangeTransactionObj GetFreeTransaction()
=> freeTransactionObjs.Count > 0 ? freeTransactionObjs.Pop()
: new ChangeTransactionObj();
// TODO: maybe sometimes clean this?
- private static readonly Stack freeTransactionObjs = new Stack();
+ private static readonly Stack freeTransactionObjs = new();
private sealed class ChangeTransactionObj : IDisposable
{
@@ -148,7 +149,7 @@ namespace IPA.Config.Stores
try
{
if (data.ownsWrite)
- data.impl.ReleaseWrite();
+ data.impl?.ReleaseWrite();
}
catch
{
@@ -163,17 +164,17 @@ namespace IPA.Config.Stores
~ChangeTransactionObj() => Dispose(false);
}
- public static Impl FindImpl(IGeneratedStore store)
+ public static Impl? FindImpl(IGeneratedStore store)
{
while (store?.Parent != null) store = store.Parent; // walk to the top of the tree
return store?.Impl;
}
internal static MethodInfo ImplReadFromMethod = typeof(Impl).GetMethod(nameof(ImplReadFrom));
- public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).ReadFrom(provider);
+ public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.ReadFrom(provider);
public void ReadFrom(ConfigProvider provider)
{
- Logger.config.Debug($"Generated impl ReadFrom {generated.GetType()}");
+ Logger.Config.Debug($"Generated impl ReadFrom {generated.GetType()}");
var values = provider.Load();
//Logger.config.Debug($"Read {values}");
generated.Deserialize(values);
@@ -183,10 +184,10 @@ namespace IPA.Config.Stores
}
internal static MethodInfo ImplWriteToMethod = typeof(Impl).GetMethod(nameof(ImplWriteTo));
- public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).WriteTo(provider);
+ public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.WriteTo(provider);
public void WriteTo(ConfigProvider provider)
{
- Logger.config.Debug($"Generated impl WriteTo {generated.GetType()}");
+ Logger.Config.Debug($"Generated impl WriteTo {generated.GetType()}");
var values = generated.Serialize();
//Logger.config.Debug($"Serialized {values}");
provider.Store(values);
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs
index d9248c21..001e0393 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
@@ -21,7 +22,7 @@ namespace IPA.Config.Stores
internal static partial class GeneratedStoreImpl
{
- internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore parent);
+ internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore? parent);
private static void GetMethodThis(ILGenerator il) => il.Emit(OpCodes.Ldarg_0);
private static (GeneratedStoreCreator ctor, Type type) MakeCreator(Type type)
@@ -52,7 +53,7 @@ namespace IPA.Config.Stores
var structure = ReadObjectMembers(type);
if (!structure.Any())
- Logger.config.Warn($"Custom type {type.FullName} has no accessible members");
+ Logger.Config.Warn($"Custom type {type.FullName} has no accessible members");
#endregion
var typeBuilder = Module.DefineType($"{type.FullName}",
@@ -109,7 +110,7 @@ namespace IPA.Config.Stores
const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
#region INotifyPropertyChanged
- MethodBuilder notifyChanged = null;
+ MethodBuilder? notifyChanged = null;
if (isINotifyPropertyChanged || hasNotifyAttribute)
{
// we don't actually want to notify if the base class implements it
@@ -134,7 +135,7 @@ namespace IPA.Config.Stores
}
else
{
- Logger.log.Critical($"Type '{type.FullName}' implements INotifyPropertyChanged but does not have an accessible " +
+ Logger.Default.Critical($"Type '{type.FullName}' implements INotifyPropertyChanged but does not have an accessible " +
"'RaisePropertyChanged(string)' method, automatic raising of PropertyChanged event is disabled.");
}
}
@@ -435,7 +436,7 @@ namespace IPA.Config.Stores
var IConfigStore_ReadFrom = IConfigStore_t.GetMethod(nameof(IConfigStore.ReadFrom));
#region IConfigStore.SyncObject
- var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
+ var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, IConfigStore_GetSyncObject.ReturnType, null);
var syncObjPropGet = typeBuilder.DefineMethod($"{nameof(IConfigStore.SyncObject)}", virtualPropertyMethodAttr, syncObjProp.PropertyType, Type.EmptyTypes);
syncObjProp.SetGetMethod(syncObjPropGet);
typeBuilder.DefineMethodOverride(syncObjPropGet, IConfigStore_GetSyncObject);
@@ -450,7 +451,7 @@ namespace IPA.Config.Stores
}
#endregion
#region IConfigStore.WriteSyncObject
- var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
+ var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, IConfigStore_GetWriteSyncObject.ReturnType, null);
var writeSyncObjPropGet = typeBuilder.DefineMethod($"{nameof(IConfigStore.WriteSyncObject)}", virtualPropertyMethodAttr, writeSyncObjProp.PropertyType, Type.EmptyTypes);
writeSyncObjProp.SetGetMethod(writeSyncObjPropGet);
typeBuilder.DefineMethodOverride(writeSyncObjPropGet, IConfigStore_GetWriteSyncObject);
@@ -639,7 +640,7 @@ namespace IPA.Config.Stores
#region Members
foreach (var member in structure.Where(m => m.IsVirtual))
{ // IsVirtual implies !IsField
- var prop = member.Member as PropertyInfo;
+ var prop = (PropertyInfo)member.Member;
var get = prop.GetGetMethod(true);
var set = prop.GetSetMethod(true);
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
index 86384804..54595d76 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
@@ -1,10 +1,12 @@
-using IPA.Config.Stores.Attributes;
+#nullable enable
+using IPA.Config.Stores.Attributes;
using IPA.Config.Stores.Converters;
using IPA.Logging;
using IPA.Utilities;
using IPA.Utilities.Async;
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -20,23 +22,25 @@ namespace IPA.Config.Stores
{
private class SerializedMemberInfo
{
- public string Name;
- public MemberInfo Member;
- public Type Type;
+ public string Name = null!;
+ public MemberInfo Member = null!;
+ public Type Type = null!;
public bool AllowNull;
public bool IsVirtual;
public bool IsField;
- public bool IsNullable; // signifies whether this is a Nullable
+ [MemberNotNullWhen(true, nameof(NullableWrappedType))]
+ public bool IsNullable { get; set; } // signifies whether this is a Nullable
- public bool HasConverter;
- public bool IsGenericConverter; // used so we can call directly to the generic version if it is
- public Type Converter;
- public Type ConverterBase;
- public Type ConverterTarget;
- public FieldInfo ConverterField;
+ [MemberNotNullWhen(true, nameof(Converter), nameof(ConverterBase))]
+ public bool HasConverter { get; set; }
+ public bool IsGenericConverter { get; set; } // used so we can call directly to the generic version if it is
+ public Type? Converter;
+ public Type? ConverterBase;
+ public Type? ConverterTarget;
+ public FieldInfo? ConverterField;
// invalid for objects with IsNullable false
- public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
+ public Type? NullableWrappedType => Nullable.GetUnderlyingType(Type);
// invalid for objects with IsNullable false
public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable.HasValue));
// invalid for objects with IsNullable false
@@ -73,25 +77,27 @@ namespace IPA.Config.Stores
{
if (converterAttr.UseDefaultConverterForType)
converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type));
+ if (converterAttr.UseDefaultConverterForType)
+ throw new InvalidOperationException("How did we get here?");
member.Converter = converterAttr.ConverterType;
member.IsGenericConverter = converterAttr.IsGenericConverter;
if (member.Converter.GetConstructor(Type.EmptyTypes) == null)
{
- Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not default-constructible");
+ Logger.Config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not default-constructible");
goto endConverterAttr; // is there a better control flow structure to do this?
}
if (member.Converter.ContainsGenericParameters)
{
- Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that has unfilled type parameters");
+ Logger.Config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that has unfilled type parameters");
goto endConverterAttr;
}
if (member.Converter.IsInterface || member.Converter.IsAbstract)
{
- Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not constructible");
+ Logger.Config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not constructible");
goto endConverterAttr;
}
@@ -100,18 +106,18 @@ namespace IPA.Config.Stores
{
try
{
- var conv = Activator.CreateInstance(converterAttr.ConverterType) as IValueConverter;
+ var conv = (IValueConverter)Activator.CreateInstance(converterAttr.ConverterType);
targetType = conv.Type;
}
catch
{
- Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter who's target type could not be determined");
+ Logger.Config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter who's target type could not be determined");
goto endConverterAttr;
}
}
if (targetType != member.Type)
{
- Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not of the member's type");
+ Logger.Config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not of the member's type");
goto endConverterAttr;
}
@@ -128,8 +134,7 @@ namespace IPA.Config.Stores
return true;
}
- private static readonly SingleCreationValueCache objectStructureCache
- = new SingleCreationValueCache();
+ private static readonly SingleCreationValueCache objectStructureCache = new();
private static IEnumerable ReadObjectMembers(Type type)
=> objectStructureCache.GetOrAdd(type, t => ReadObjectMembersInternal(type).ToArray());
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
index cea03460..358bef88 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections.Generic;
@@ -22,6 +23,17 @@ namespace IPA.Config.Stores
{
EmitLoad(il, member, thisarg);
+ using var valueTypeLocal =
+ member.IsNullable
+ ? GetLocal.Allocate(member.Type)
+ : default;
+
+ if (member.IsNullable)
+ {
+ il.Emit(OpCodes.Stloc, valueTypeLocal.Local);
+ il.Emit(OpCodes.Ldloca, valueTypeLocal.Local);
+ }
+
var endSerialize = il.DefineLabel();
if (member.AllowNull)
@@ -47,7 +59,7 @@ namespace IPA.Config.Stores
var targetType = GetExpectedValueTypeForType(memberConversionType);
if (member.HasConverter)
{
- using var stlocal = GetLocal.Allocate(member.Type);
+ using var stlocal = GetLocal.Allocate(memberConversionType);
using var valLocal = GetLocal.Allocate(typeof(Value));
il.Emit(OpCodes.Stloc, stlocal);
@@ -113,7 +125,7 @@ namespace IPA.Config.Stores
else if (targetType == typeof(List))
{
// TODO: impl this (enumerables)
- Logger.config.Warn($"Implicit conversions to {targetType} are not currently implemented");
+ Logger.Config.Warn($"Implicit conversions to {targetType} are not currently implemented");
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull);
}
@@ -156,7 +168,7 @@ namespace IPA.Config.Stores
var structure = ReadObjectMembers(memberConversionType);
if (!structure.Any())
{
- Logger.config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" +
+ Logger.Config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" +
$" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
il.Emit(OpCodes.Pop);
}
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs
index 43b766ed..f1a9ddb6 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections;
@@ -22,26 +23,26 @@ namespace IPA.Config.Stores
{
#region Logs
private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStoreImpl).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
- internal static void LogError(Type expected, Type found, string message)
+ internal static void LogError(Type? expected, Type? found, string message)
{
- Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
+ Logger.Config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
}
private static readonly MethodInfo LogWarningMethod = typeof(GeneratedStoreImpl).GetMethod(nameof(LogWarning), BindingFlags.NonPublic | BindingFlags.Static);
internal static void LogWarning(string message)
{
- Logger.config.Warn(message);
+ Logger.Config.Warn(message);
}
private static readonly MethodInfo LogWarningExceptionMethod = typeof(GeneratedStoreImpl).GetMethod(nameof(LogWarningException), BindingFlags.NonPublic | BindingFlags.Static);
internal static void LogWarningException(Exception exception)
{
- Logger.config.Warn(exception);
+ Logger.Config.Warn(exception);
}
#endregion
//private delegate LocalBuilder LocalAllocator(Type type, int idx = 0);
private static LocalAllocator MakeLocalAllocator(ILGenerator il)
- => new LocalAllocator(il);
+ => new(il);
private struct AllocatedLocal : IDisposable
{
@@ -59,14 +60,14 @@ namespace IPA.Config.Stores
#endif
public static implicit operator LocalBuilder(AllocatedLocal loc) => loc.Local;
- public void Dealloc() => allocator.Deallocate(this);
+ public void Dealloc() => allocator?.Deallocate(this);
public void Dispose() => Dealloc();
}
private sealed class LocalAllocator
{
private readonly ILGenerator ilSource;
- private readonly Dictionary> unallocatedLocals = new Dictionary>();
+ private readonly Dictionary> unallocatedLocals = new();
public LocalAllocator(ILGenerator il)
=> ilSource = il;
@@ -81,7 +82,7 @@ namespace IPA.Config.Stores
{
var list = GetLocalListForType(type);
if (list.Count < 1) list.Push(ilSource.DeclareLocal(type));
- return new AllocatedLocal(this, list.Pop());
+ return new(this, list.Pop());
}
public void Deallocate(AllocatedLocal loc)
@@ -97,12 +98,13 @@ namespace IPA.Config.Stores
thisarg(il); // load this
if (member.IsField)
- il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
+ il.Emit(OpCodes.Ldfld, (FieldInfo)member.Member);
else
{ // member is a property
- var prop = member.Member as PropertyInfo;
- var getter = prop.GetGetMethod();
- if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
+ var prop = (PropertyInfo)member.Member;
+ var getter = prop.GetGetMethod(true);
+ if (getter is null || getter.IsPrivate)
+ throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
il.Emit(OpCodes.Call, getter);
}
@@ -114,12 +116,13 @@ namespace IPA.Config.Stores
value(il);
if (member.IsField)
- il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
+ il.Emit(OpCodes.Stfld, (FieldInfo)member.Member);
else
{ // member is a property
- var prop = member.Member as PropertyInfo;
- var setter = prop.GetSetMethod();
- if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
+ var prop = (PropertyInfo)member.Member;
+ var setter = prop.GetSetMethod(true);
+ if (setter is null || setter.IsPrivate)
+ throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
il.Emit(OpCodes.Call, setter);
}
@@ -132,7 +135,7 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Call, LogWarningExceptionMethod);
}
- private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action expected = null, Action found = null)
+ private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action? expected = null, Action? found = null)
{
if (expected == null) expected = il => il.Emit(OpCodes.Ldnull);
if (found == null) found = il => il.Emit(OpCodes.Ldnull);
diff --git a/IPA.Loader/Config/Stores/GeneratedStorePublicInterface.cs b/IPA.Loader/Config/Stores/GeneratedStorePublicInterface.cs
index 7ece8174..c768c1be 100644
--- a/IPA.Loader/Config/Stores/GeneratedStorePublicInterface.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStorePublicInterface.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Stores.Attributes;
+#nullable enable
+using IPA.Config.Stores.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
diff --git a/IPA.Loader/Config/Stores/ValueConverter.cs b/IPA.Loader/Config/Stores/ValueConverter.cs
index 8be1f300..68cec79e 100644
--- a/IPA.Loader/Config/Stores/ValueConverter.cs
+++ b/IPA.Loader/Config/Stores/ValueConverter.cs
@@ -1,4 +1,5 @@
-using IPA.Config.Data;
+#nullable enable
+using IPA.Config.Data;
using System;
namespace IPA.Config.Stores
@@ -31,14 +32,14 @@ namespace IPA.Config.Stores
/// the object to convert
/// the owning object of
/// a representation of as a structure
- Value ToValue(object obj, object parent);
+ Value? ToValue(object? obj, object parent);
///
/// Converts the given to the object type handled by this converter.
///
/// the to deserialize
/// the object that will own the result
/// the deserialized object
- object FromValue(Value value, object parent);
+ object? FromValue(Value? value, object parent);
///
/// Gets the type that this handles.
///
@@ -59,7 +60,7 @@ namespace IPA.Config.Stores
/// the owning object of
/// a representation of as a structure
///
- public abstract Value ToValue(T obj, object parent);
+ public abstract Value? ToValue(T? obj, object parent);
///
/// Converts the given to the object type handled by this converter.
///
@@ -67,10 +68,10 @@ namespace IPA.Config.Stores
/// the object that will own the result
/// the deserialized object
///
- public abstract T FromValue(Value value, object parent);
+ public abstract T? FromValue(Value? value, object parent);
- Value IValueConverter.ToValue(object obj, object parent) => ToValue((T)obj, parent);
- object IValueConverter.FromValue(Value value, object parent) => FromValue(value, parent);
+ Value? IValueConverter.ToValue(object? obj, object parent) => ToValue((T?)obj, parent);
+ object? IValueConverter.FromValue(Value? value, object parent) => FromValue(value, parent);
Type IValueConverter.Type => typeof(T);
}
}
diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj
index 3cc88a4f..ff62c1c3 100644
--- a/IPA.Loader/IPA.Loader.csproj
+++ b/IPA.Loader/IPA.Loader.csproj
@@ -3,16 +3,16 @@
- net461;net35
+ net472
IPA
true
true
false
- true
+ true
-
+
$(DefineConstants);NET4
@@ -22,7 +22,7 @@
$(DefineConstants);BeatSaber
-
+
..\Refs\UnityEngine.CoreModule.Net4.dll
False
@@ -49,12 +49,19 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -69,6 +76,11 @@
+
+
+
+
+
diff --git a/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs b/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs
index 97cccb18..d17c5781 100644
--- a/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs
+++ b/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs
@@ -3,27 +3,49 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace IPA.JsonConverters
{
- internal class FeaturesFieldConverter : JsonConverter>
+ internal class FeaturesFieldConverter : JsonConverter>>
{
- public override Dictionary ReadJson(JsonReader reader, Type objectType, Dictionary existingValue, bool hasExistingValue, JsonSerializer serializer)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Assert([DoesNotReturnIf(false)] bool condition)
+ {
+ if (!condition)
+ throw new InvalidOperationException();
+ }
+
+ public override Dictionary> ReadJson(JsonReader reader, Type objectType, Dictionary> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
_ = serializer.Deserialize(reader);
- Logger.features.Warn("Encountered old features used. They no longer do anything, please move to the new format.");
+ Logger.Features.Warn("Encountered old features used. They no longer do anything, please move to the new format.");
return existingValue;
}
- return serializer.Deserialize